using System;
using System.Collections.Generic;
using System.Text;
using MacomberMap.Common.User_Interfaces.One_Lines;
using MacomberMap.Common.User_Interfaces.Components;
using System.Windows.Forms;
using MacomberMap.Common.Database_Interface.CIM.Components;
using MacomberMap.Common.Components;
using MacomberMap.Common.User_Interfaces.One_Lines.Elements;
using System.Drawing;
using System.Xml;
using System.IO;
using MacomberMap.Common.Logging;
using MacomberMap.Common.Events;
using MacomberMap.Common.Database_Interface.CIM.Events;
using MacomberMap.Common.Internals;

namespace MacomberMap.Common.Database_Interface.CIM.One_Line_Interface
{
    /// <summary>
    /// This class handles the DDL XML to MM One-line element conversions
    /// </summary>
    public static class MM_DDL_Importer
    {
        #region Variable declarations
        /// <summary>The substation element </summary>
        public static CIM_Element SubElement;
        #endregion

        /// <summary>
        /// Go through all elements, and match them appropriately.
        /// </summary>
        /// <param name="OLComponents">The one-line elements</param>
        /// <param name="TuningParameters">The tuning parameters</param>        
        /// <param name="OLView">The one-line viewer</param>
        /// <param name="Linkages">The linkages between elements</param>
        public static void MatchElements(Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents, List<MM_OneLine_TuningParameter> TuningParameters, MM_OneLine_Viewer OLView, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Linkages)
        {
            int MatchCount = 0;
            List<MM_OneLine_Element> AssignedElements = new List<MM_OneLine_Element>();
            //First, build our dictionary of synchroscopes            
            Dictionary<String, bool> Synchroscopes = new Dictionary<string, bool>(StringComparer.CurrentCultureIgnoreCase);
            using (StreamReader sRd = new StreamReader(Application.StartupPath + "\\Synchroscopes.txt", true))
                while (!sRd.EndOfStream)
                {
                    String NewLine = sRd.ReadLine();
                    if (!Synchroscopes.ContainsKey(NewLine))
                        Synchroscopes.Add(NewLine, true);
                }

            foreach (KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_Element> kvp in OLComponents)
            {

                MatchElement(kvp.Key, kvp.Value, ref MatchCount, OLView, OLComponents, TuningParameters, AssignedElements);

                //If our element is a breaker, check against synchroscope list and update accordingly.
                if (kvp.Value is MM_OneLine_Breaker && kvp.Key.AssociatedElement != null && (Synchroscopes.ContainsKey(kvp.Key.AssociatedElement.Substation.Name + "." + kvp.Key.AssociatedElement.Name) || Synchroscopes.ContainsKey(kvp.Key.AssociatedElement.Substation.Name)))
                    (kvp.Value as MM_OneLine_Breaker).HasSynchroscope = true;
            }

            //Now, make a second pass. If we have an unallocated busbar section, let's handle it
            foreach (KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_Element> kvp in OLComponents)
                if (kvp.Value is MM_OneLine_Node && kvp.Value.BaseElement == null)
                    try
                    {
                        Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> OutList;
                        LocateNode(kvp.Key, OLComponents, Linkages, out OutList);
                        Dictionary<CIM_Element, int> Matches = new Dictionary<CIM_Element, int>();
                        foreach (MM_OneLine_XmlComponent Component in OutList.Keys)
                            if (Component.AssociatedElement != null)
                                foreach (CIM_Element Node in Component.AssociatedElement.Nodes)
                                    if (Matches.ContainsKey(Node))
                                        Matches[Node]++;
                                    else
                                        Matches.Add(Node, 1);

                        bool RemovedAny = false;
                        CIM_Element Match = null;
                        do
                        {
                            RemovedAny = false;
                            foreach (KeyValuePair<CIM_Element, int> kvp2 in Matches)
                                if (kvp2.Value == OutList.Count)
                                    Match = kvp2.Key;
                                else
                                {
                                    Matches.Remove(kvp2.Key);
                                    RemovedAny = true;
                                    break;
                                }
                        } while (RemovedAny);

                        if (Matches.Count == 1)
                        {
                            kvp.Value.BaseElement = MM_Database_CIM.LocateElement(kvp.Key.AssociatedElement = Match, OLView.AddedElements);
                            AssignedElements.Add(kvp.Value);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error matching busbar section: " + ex.Message);
                    }
                   
            OLView.DisplayParameters.HandleAssignedElements(AssignedElements.ToArray());
            MM_Log.LogEvent(new MM_Event_Information(String.Format("Matched {0:#,##0} data-bound elements between DDL and CIM.", MatchCount), typeof(MM_DDL_Importer)));
        }

        /// <summary>
        /// Match a single element against its information
        /// </summary>
        /// <param name="XmlComponent">The XML component</param>
        /// <param name="Element">The MM Element</param>
        /// <param name="MatchCount">Our tracker of matching</param>
        /// <param name="OLComponents">The one-line components</param>
        /// <param name="OLView">The one-line viewer</param>
        /// <param name="TuningParameters">The tuning parameters</param>        
        /// <param name="AssignedElements">The collection of assigned elements</param>
        public static void MatchElement(MM_OneLine_XmlComponent XmlComponent, MM_OneLine_Element Element, ref int MatchCount, MM_OneLine_Viewer OLView, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents, List<MM_OneLine_TuningParameter> TuningParameters, List<MM_OneLine_Element> AssignedElements)
        {            
            KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_Element> kvp = new KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_Element>(XmlComponent, Element);
            if (kvp.Value is MM_OneLine_Breaker || kvp.Value is MM_OneLine_Switch || kvp.Value is MM_OneLine_Unit || kvp.Value is MM_OneLine_Load || kvp.Value is MM_OneLine_Capacitor || kvp.Value is MM_OneLine_Reactor || kvp.Value is MM_OneLine_StaticVarCompensator || kvp.Value is MM_OneLine_Line || kvp.Value is MM_OneLine_Node || kvp.Value is MM_OneLine_PokePoint)
            {
                MM_OneLine_Element.enumOrientations Orientation;
                kvp.Value.BaseElement = LocateElement(kvp.Key, OLComponents, TuningParameters, out Orientation, OLView.AddedElements);
                kvp.Value.Orientation = Orientation;
                if (kvp.Value.BaseElement != null)
                {
                    kvp.Value.rdfID = MM_Database_CIM.TEIDs[kvp.Value.BaseElement.TEID].rdfID;
                    if (kvp.Value is MM_OneLine_Breaker)
                        (kvp.Value as MM_OneLine_Breaker).Opened = (kvp.Value as MM_OneLine_Breaker).ScadaOpened = (bool.Parse(kvp.Key.AssociatedElement["cim:Switch.normalOpen"]) ? CheckState.Checked : CheckState.Unchecked);
                    else if (kvp.Value is MM_OneLine_Switch)
                        (kvp.Value as MM_OneLine_Switch).Opened = (kvp.Value as MM_OneLine_Switch).ScadaOpened = (bool.Parse(kvp.Key.AssociatedElement["cim:Switch.normalOpen"]) ? CheckState.Checked : CheckState.Unchecked);
                    else if (kvp.Value is MM_OneLine_Node)
                        (kvp.Value as MM_OneLine_Node).ResourceNode = MM_Database_CIM.TEIDs[kvp.Value.BaseElement.TEID]["etx:ElectricalBus>etx:ResourceNode>Name"];
                    
                    AssignedElements.Add(kvp.Value);
                    MatchCount++;
                    Element.BringToFront();
                }
                else
                {
                }

            }
            else if (kvp.Value is MM_OneLine_Transformer)
            {
                MM_OneLine_Transformer XF = kvp.Value as MM_OneLine_Transformer;
                for (int a = 0; a < kvp.Key.SubElements.Count; a++)
                {
                    MM_OneLine_Element.enumOrientations Orientation;
                    if (XF.Windings[1].IsPhaseShifter)
                        XF.Windings[a].BaseElement = LocateElement(kvp.Key, OLComponents, TuningParameters, out Orientation, OLView.AddedElements);
                    else
                        XF.Windings[a].BaseElement = LocateElement(kvp.Key.SubElements[a], OLComponents, TuningParameters, out Orientation, OLView.AddedElements);
                    XF.Windings[a].Orientation = Orientation;
                    if (XF.Windings[a].BaseElement != null)
                    {
                        XF.Windings[a].rdfID = MM_Database_CIM.TEIDs[XF.Windings[a].BaseElement.TEID].rdfID;
                        if (!OLView.TransformerWindings.ContainsKey(XF.Windings[a].BaseElement))
                            OLView.TransformerWindings.Add(XF.Windings[a].BaseElement, XF);
                        MatchCount++;
                    }

                }
                
                if (kvp.Key.SubElements.Count > 0 && kvp.Key.SubElements[0].AssociatedElement != null)
                    foreach (CIM_Element XFType in kvp.Key.SubElements[0].AssociatedElement.Links)
                        if (XFType.Type == "cim:PowerTransformer")
                        {
                            XF.BaseElement = MM_Database_CIM.LocateElement(kvp.Key.AssociatedElement = XFType, OLView.AddedElements);
                            XF.rdfID = XFType.rdfID;
                            AssignedElements.Add(XF);                            
                        }
                Element.Orientation = Element.Orientation;                
                Element.BringToFront();
            }
        }
            

        /// <summary>
        /// Import elements from a one-line XML into our UI space
        /// </summary>
        /// <param name="OLComponents">The XML components within the CIM file</param>
        /// <param name="OLViewer">The One-line viewer</param>
        /// <param name="tTip">The tooltip that will display for the element</param>
        /// <param name="TuningParameters">The tuning parameters for matching elements</param>
        /// <param name="DisplayParams">The display parameters for the UI</param>
        /// <param name="Linkages">The linkages between elements</param>
        /// <param name="Elements">The collection of CIM-linked elements</param>
        /// <param name="SubElement">The substation CIM element</param>
        public static void ImportElements(Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents, MM_OneLine_Viewer OLViewer, ToolTip tTip, List<MM_OneLine_TuningParameter> TuningParameters, MM_OneLine_Display DisplayParams,  Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Linkages, out Dictionary<MM_OneLine_Element, MM_OneLine_XmlComponent> Elements, CIM_Element SubElement)
        {
            //Now, import all elements that fit our specifications.
            MM_OneLine_Element NewControl;
            Elements = new Dictionary<MM_OneLine_Element, MM_OneLine_XmlComponent>();
            OLViewer.ExportDate = DateTime.Now;
            OLViewer.LastCIM = Path.GetFileNameWithoutExtension(MM_Database_CIM.CIMFileName);
            List<MM_OneLine_Element> NewElems = new List<MM_OneLine_Element>();
            foreach (MM_OneLine_XmlComponent Component in new List<MM_OneLine_XmlComponent>(OLComponents.Keys))
            {
                if (Component.Name.Contains("KV_"))
                {
                    //We have an element that should be data linked. First, find the element in question
                    MM_OneLine_Element.enumOrientations Orientation = FindElementOrientation(Component, MM_OneLine_Element.ElementTypeEnum.Unknown);
                        

                    String SubName = Component.BaseElement.ParentNode.ParentNode.Attributes["Name"].Value;
                    CIM_Element OwnerElement = MM_Database_CIM.LocateElement(SubName, "Substation", SubName, "");
                    NewControl = null;
                    //NewControl = new MM_OneLine_Element_Old(Component, OLComponents, tTip, MM_OneLine_Element_Old.ElementTypeEnum.Breaker, OwnerElement, TuningParameters, DisplayParams, Linkages);
                    if (Component.Name.StartsWith("CB_NET"))
                        NewControl = new MM_OneLine_Breaker(null, DisplayParams, Orientation);
                    else if (Component.Name.StartsWith("SW_NET_"))
                        NewControl = new MM_OneLine_Switch(null, DisplayParams, Orientation);
                    else if (Component.Name.StartsWith("GEN_") && !Component.Name.StartsWith("GEN_LABEL_"))
                        NewControl = new MM_OneLine_Unit(null, DisplayParams, Orientation);
                    else if (Component.Name.StartsWith("LOAD_"))
                        NewControl = new MM_OneLine_Load(null, DisplayParams, Orientation);
                    else if (Component.Name.StartsWith("CAPACITOR_"))
                        NewControl = new MM_OneLine_Capacitor(null, DisplayParams, Orientation);
                    else if (Component.Name.StartsWith("REACTOR_"))
                        NewControl = new MM_OneLine_Reactor(null, DisplayParams, Orientation);
                    else if (Component.Name.StartsWith("SVC_"))
                        NewControl = new MM_OneLine_StaticVarCompensator(null, DisplayParams, Orientation);
                    else if (Component.Name.StartsWith("LINE_"))
                        NewControl = new MM_OneLine_Line(null, DisplayParams, Orientation);
                    else if (Component.Name.EndsWith("KV_BUS"))
                        NewControl = new MM_OneLine_Node(null, DisplayParams, Orientation);
                    else if (Component.Name.StartsWith("TRANS_PHASE_"))
                    {
                        NewControl = new MM_OneLine_Transformer(null, DisplayParams, Orientation);
                        MM_OneLine_TransformerWinding[] Winding;
                        Point Center = MM_OneLine_Element.CenterRect(new Rectangle(Point.Empty, NewControl.Size));
                        if (Orientation == MM_OneLine_Element.enumOrientations.Left || Orientation == MM_OneLine_Element.enumOrientations.Right || Orientation == MM_OneLine_Element.enumOrientations.Horizontal || Orientation == MM_OneLine_Element.enumOrientations.Unknown)
                            Winding = new MM_OneLine_TransformerWinding[] { new MM_OneLine_TransformerWinding(null, new Rectangle(0, 0, Center.X, NewControl.Height), MM_OneLine_Element.enumOrientations.Right), new MM_OneLine_TransformerWinding(null, new Rectangle(Center.X, 0, Center.X, NewControl.Height), MM_OneLine_Element.enumOrientations.Left) };
                        else
                            Winding = new MM_OneLine_TransformerWinding[] { new MM_OneLine_TransformerWinding(null, new Rectangle(0, 0, NewControl.Width, Center.Y), MM_OneLine_Element.enumOrientations.Down), new MM_OneLine_TransformerWinding(null, new Rectangle(0, Center.Y, NewControl.Width, Center.Y), MM_OneLine_Element.enumOrientations.Up) };                        
                        Winding[1].IsPhaseShifter = true;
                        (NewControl as MM_OneLine_Transformer).Windings = Winding;                            
                    }
                    else if (Component.Name.StartsWith("TRANS_"))
                    {
                        MM_OneLine_TransformerWinding[] Winding = new MM_OneLine_TransformerWinding[Component.SubElements.Count];
                        for (int a = 0; a < Component.SubElements.Count; a++)
                            Winding[a] = new MM_OneLine_TransformerWinding(null, new Rectangle(Component.SubElements[a].ElementRectangle.X - Component.ElementRectangle.X, Component.SubElements[a].ElementRectangle.Y - Component.ElementRectangle.Y, Component.SubElements[a].ElementRectangle.Width, Component.SubElements[a].ElementRectangle.Height), Orientation);
                        NewControl = new MM_OneLine_Transformer(null, DisplayParams, Orientation);
                        (NewControl as MM_OneLine_Transformer).Windings = Winding;
                    }
                    else
                        NewControl = new MM_OneLine_UnlinkedElement(DisplayParams);

                    if (NewControl is MM_OneLine_UnlinkedElement == false)
                        Elements.Add(NewControl, Component);
                }
                else
                    NewControl = new MM_OneLine_UnlinkedElement(DisplayParams);


                OLComponents[Component] = NewControl;
                NewControl.Tag = Component;                
                NewControl.Bounds = Component.ElementRectangle;
                NewElems.Add(NewControl);
            }
            DisplayParams.HandleAddedElements(NewElems.ToArray());
            MM_Log.LogEvent(new MM_Event_Information(String.Format("Retrieved {0} data-bound elements from {1}", Elements.Count, SubElement.Name), typeof(MM_DDL_Importer)));
        }

        /// <summary>
        /// Determine the element type based on component name
        /// </summary>
        /// <param name="Component">The component that is being checked</param>
        /// <returns></returns>
        private static MM_OneLine_Element.ElementTypeEnum FindElementType(MM_OneLine_XmlComponent Component)
        {
            if (Component.Name.StartsWith("CB_NET"))
                return MM_OneLine_Element.ElementTypeEnum.Breaker;
            else if (Component.Name.StartsWith("SW_NET_"))
                return MM_OneLine_Element.ElementTypeEnum.Switch;
            else if (Component.Name.StartsWith("GEN_") && !Component.Name.StartsWith("GEN_LABEL_"))
                return MM_OneLine_Element.ElementTypeEnum.Unit;
            else if (Component.Name.StartsWith("LOAD_"))
                return MM_OneLine_Element.ElementTypeEnum.Load;
            else if (Component.Name.StartsWith("CAPACITOR_"))
                return MM_OneLine_Element.ElementTypeEnum.Capacitor;
            else if (Component.Name.StartsWith("REACTOR_"))
                return MM_OneLine_Element.ElementTypeEnum.Reactor;
            else if (Component.Name.StartsWith("SVC_"))
                return MM_OneLine_Element.ElementTypeEnum.StaticVarCompensator;
            else if (Component.Name.StartsWith("TRANS_"))
                return MM_OneLine_Element.ElementTypeEnum.Transformer;
            else if (Component.Name.StartsWith("LINE_"))
                return MM_OneLine_Element.ElementTypeEnum.Line;
            else if (Component.Name.EndsWith("KV_BUS"))
                return MM_OneLine_Element.ElementTypeEnum.Bus;
            else
                return MM_OneLine_Element.ElementTypeEnum.Unknown;
        }

        #region Orientation determination
        /// <summary>
        /// Determine an element's orientation by its parameters
        /// </summary>
        /// <param name="ElementToCheck">The element to check</param>
        /// <param name="ElemType">The type of element to check</param>
        /// <returns></returns>
        private static MM_OneLine_Element.enumOrientations FindElementOrientation(MM_OneLine_XmlComponent ElementToCheck, MM_OneLine_Element.ElementTypeEnum ElemType)
        {
            if (ElementToCheck.BaseElement.Name == "polyline")
                return ElementToCheck.ElementRectangle.Width > ElementToCheck.ElementRectangle.Height ? MM_OneLine_Element.enumOrientations.Horizontal : MM_OneLine_Element.enumOrientations.Vertical;

            String EndElement = ElementToCheck.Name.Substring(ElementToCheck.Name.LastIndexOf('_'));
            if (EndElement == "_H")
                return MM_OneLine_Element.enumOrientations.Horizontal;
            else if (EndElement == "_V")
                return MM_OneLine_Element.enumOrientations.Vertical;
            else if (EndElement == "_L")
                return MM_OneLine_Element.enumOrientations.Left;
            else if (EndElement == "_R")
                return MM_OneLine_Element.enumOrientations.Right;
            else if (EndElement == "_U")
                return MM_OneLine_Element.enumOrientations.Up;
            else if (EndElement == "_D")
                return MM_OneLine_Element.enumOrientations.Down;
            return MM_OneLine_Element.enumOrientations.Unknown;
        }
        #endregion


        /// <summary>
        /// If not null, add a located element to the list of elements
        /// </summary>
        /// <param name="Elements"></param>
        /// <param name="Element"></param>
        private static void AddPotentialElement(List<CIM_Element> Elements, CIM_Element Element)
        {
            if (Element != null && !Elements.Contains(Element))
                Elements.Add(Element);
        }


      
        /// <summary>
        /// Locate a CIM element from an XML component, and create an MM element if we can
        /// </summary>
        /// <param name="OLComponent">The component to check for</param>
        /// <param name="OLComponents">The collection of all Xml components</param>
        /// <param name="TuningParameters">The tuning parameters for components</param>
        /// <param name="Orientation">The orientation of the element</param>        
        /// <param name="ExistingElements"></param>
        /// <returns></returns>        
        private static MM_Element LocateElement(MM_OneLine_XmlComponent OLComponent, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents, List<MM_OneLine_TuningParameter> TuningParameters, out MM_OneLine_Element.enumOrientations Orientation, Dictionary<UInt64, MM_Element> ExistingElements)
        {
            List<CIM_Element> AssociatedElement = new List<CIM_Element>();
            AddPotentialElement(AssociatedElement, OLComponent.AssociatedElement);
           
            OLComponent.AssociatedElement = null;

            //First, determine our element type and orientation
            MM_OneLine_Element.ElementTypeEnum ElementType = FindElementType(OLComponent);
            Orientation = FindElementOrientation(OLComponent, ElementType);


            //Try and locate a CIM element corresponding to this item
            if (OLComponent.BaseElement["composite_key"] != null)
            {
                string[] splStr = OLComponent.BaseElement["composite_key"].Attributes["record_key"].Value.Split(',');
                //TODO: Update based on the element type being references
                if (ElementType == MM_OneLine_Element.ElementTypeEnum.Breaker)
                    AddPotentialElement(AssociatedElement, MM_Database_CIM.LocateElement(splStr[0], "Breaker", splStr[2], ""));
                else if (ElementType == MM_OneLine_Element.ElementTypeEnum.Switch)
                    AddPotentialElement(AssociatedElement, MM_Database_CIM.LocateElement(splStr[0], "Disconnector", splStr[2], ""));
                else if (ElementType == MM_OneLine_Element.ElementTypeEnum.Unit)
                    AddPotentialElement(AssociatedElement, MM_Database_CIM.LocateElement(splStr[0], "SynchronousMachine", splStr[1], ""));
                else
                    Console.WriteLine("Unknown element type with composite key: " + OLComponent.BaseElement.Attributes["Name"].Value);
            }


            //Check our tuning parameters to find ones that fit
            foreach (MM_OneLine_TuningParameter Param in TuningParameters)
                if (Param.ElemType == ElementType && (Param.Orientation == MM_OneLine_Element.enumOrientations.Unknown || Param.Orientation == Orientation))
                    foreach (MM_OneLine_XmlComponent Component in OLComponents.Keys)
                        if (Param.Check(Component, OLComponent) || (OLComponent.SubElements != null && OLComponent.SubElements.Contains(Component)))
                            if ((Param.ElemType != MM_OneLine_Element.ElementTypeEnum.Breaker && Param.ElemType != MM_OneLine_Element.ElementTypeEnum.Switch) || OLComponent.Key == Component.Key)
                            {
                                if (OLComponent.SubElements == null)
                                    OLComponent.SubElements = new List<MM_OneLine_XmlComponent>(10);
                                if (!OLComponent.SubElements.Contains(Component))
                                    OLComponent.SubElements.Add(Component);
                                if (Component.Parent != null && Component.Parent != OLComponent && Component.Name != "TRANS_LABEL_NET")
                                    MM_Log.LogEvent(new MM_Event_MoreParents(Component, Component.Parent, OLComponent));
                                Component.Parent = OLComponent;
                                Component.Processed = true;
                            }

            //Now, go through all sub-elements and handle accordingly
            CIM_Element Specific = null;
            if (OLComponent.SubElements != null)
                foreach (MM_OneLine_XmlComponent Component in OLComponent.SubElements)
                {
                    String Key = Component.Key;
                    if (!String.IsNullOrEmpty(Key))
                    {
                        String[] splStr = Key.Split('=', ',');
                        if (splStr.Length == 1)
                        {
                            //If we have a title element w/ text, try and match up that way.
                            if (ElementType == MM_OneLine_Element.ElementTypeEnum.Load)
                                AddPotentialElement(AssociatedElement, MM_Database_CIM.LocateElement(SubElement.Name, "Load", splStr[0], ""));
                            else if (ElementType == MM_OneLine_Element.ElementTypeEnum.Line)
                            {
                                foreach (CIM_Element Node in SubElement.Nodes)
                                    foreach (CIM_Element Elem in Node.Elements)
                                        if (Elem.Type == "cim:ACLineSegment" || Elem.Type == "cim:SeriesCompensator")
                                            foreach (CIM_Element Stn in Elem.ACSegmentStations)
                                                if (String.Equals(Stn["cim:IdentifiedObject.description"], splStr[splStr.Length - 1], StringComparison.CurrentCultureIgnoreCase))
                                                    AddPotentialElement(AssociatedElement, Elem); //.Parse("cim:Line"));
                            }
                            else if (ElementType == MM_OneLine_Element.ElementTypeEnum.Reactor)
                            {
                                CIM_Element FoundElem = MM_Database_CIM.LocateElement(SubElement.Name, "Line", splStr[0], "");
                                if (FoundElem != null)
                                    AddPotentialElement(AssociatedElement, FoundElem);
                            }
                            else
                            { }
                        }
                        else if (splStr.Length >= 2)
                        {
                            //Now, use the last element type to determine the match
                            if (splStr[splStr.Length - 2] == "LD")
                                AddPotentialElement(AssociatedElement, MM_Database_CIM.LocateElement(splStr[1], "Load", splStr[splStr.Length - 1], ""));
                            else if (splStr[splStr.Length - 2] == "ST")
                            {
                                foreach (CIM_Element Node in SubElement.Nodes)
                                    foreach (CIM_Element Elem in Node.Elements)
                                        if (Elem.Type == "cim:ACLineSegment" || Elem.Type == "cim:SeriesCompensator")
                                            foreach (CIM_Element Stn in Elem.ACSegmentStations)
                                                if (String.Equals(Stn.Name, splStr[splStr.Length - 1], StringComparison.CurrentCultureIgnoreCase))
                                                    AddPotentialElement(AssociatedElement, Elem);
                            }

                            else if ((splStr[splStr.Length - 2] == "LN2" || splStr[splStr.Length - 2] == "ZBR2") && splStr[splStr.Length - 4] == "ND")
                            {
                                //First, attempt to find the line in question by name
                                CIM_Element Line = MM_Database_CIM.LocateElement(splStr[1], "Line", splStr[splStr.Length - 1], "");
                                if (Line != null)
                                    AddPotentialElement(AssociatedElement, Specific = Line);
                                else
                                {
                                    CIM_Element Node = MM_Database_CIM.LocateElement(splStr[1], "ConnectivityNode", splStr[splStr.Length - 3], "");
                                    CIM_Element Seg = null;
                                    if (Node != null && (Seg = Node.Parse("cim:Terminal>cim:ACLineSegment")) != null)
                                        AddPotentialElement(AssociatedElement, Specific = Seg);
                                    else if (Node != null && (Seg = Node.Parse("cim:Terminal>cim:SeriesCompensator")) != null)
                                        AddPotentialElement(AssociatedElement, Specific = Seg);
                                }
                            }
                            else if (splStr[splStr.Length - 2] == "CB" && splStr[splStr.Length - 3] == "CB")
                                AddPotentialElement(AssociatedElement, Specific = MM_Database_CIM.LocateElement(splStr[1], "Breaker", splStr[splStr.Length - 1], ""));

                            else if (splStr[splStr.Length - 2] == "CB" && splStr[splStr.Length - 3] == "DSC")
                                AddPotentialElement(AssociatedElement, Specific = MM_Database_CIM.LocateElement(splStr[1], "Disconnector", splStr[splStr.Length - 1], ""));
                            else if (splStr[splStr.Length - 2] == "UN")
                                AddPotentialElement(AssociatedElement, Specific = MM_Database_CIM.LocateElement(splStr[1], "Unit", splStr[splStr.Length - 1], ""));
                            else if (splStr[splStr.Length - 2] == "XF")
                            {
                                CIM_Element Trans = MM_Database_CIM.LocateElement(splStr[1], "TransformerWinding", splStr[splStr.Length - 1], "");
                                if (Trans != null)
                                    Trans = Trans.Parse("cim:PowerTransformer");
                                if (Trans != null)
                                {
                                    Common.Types.MM_KVLevel KVLevel = MM_Overall_Display.FindKVLevel(OLComponent.Name.Split('_')[1]);
                                    foreach (CIM_Element Winding in Trans.Elements)
                                        if (KVLevel == MM_Overall_Display.FindKVLevel(Winding.VoltageLevel.Name))
                                            AddPotentialElement(AssociatedElement, Winding);
                                }
                            }
                            else if (splStr[splStr.Length - 4] == "ND" && splStr[splStr.Length - 2] == "XF2")
                            {
                                CIM_Element BaseNode = null; // MM_Database_CIM.LocateElement(splStr[1], "ConnectivityNode", splStr[splStr.Length - 1], "");
                                if (BaseNode == null)
                                {
                                    int LastUnderline = OLComponent.Name.LastIndexOf('_') - 1;
                                    int SecondToLastUnderLine = OLComponent.Name.LastIndexOf('_', LastUnderline) + 1;
                                    Common.Types.MM_KVLevel KVLevel = MM_Overall_Display.FindKVLevel(OLComponent.Name.Substring(SecondToLastUnderLine, 1 + LastUnderline - SecondToLastUnderLine));
                                    CIM_Element FoundXF = MM_Database_CIM.LocateElement(splStr[1], "TransformerWinding", splStr[splStr.Length - 3], "");
                                    if (FoundXF != null)
                                        foreach (CIM_Element Winding in FoundXF.Parse("cim:PowerTransformer").Elements)
                                            if (MM_Overall_Display.FindKVLevel(Winding.VoltageLevel.Name) == KVLevel)
                                                AddPotentialElement(AssociatedElement, Specific = Winding);
                                }
                                else
                                    foreach (CIM_Element Elem in BaseNode.Elements)
                                        if (Elem.Type == "cim:TransformerWinding")
                                            AddPotentialElement(AssociatedElement, Specific = Elem);
                            }
                            else if (splStr[splStr.Length - 2] == "ND")
                                AddPotentialElement(AssociatedElement, MM_Database_CIM.LocateElement(splStr[1], "ConnectivityNode", splStr[splStr.Length - 1], ""));
                            else if (splStr[splStr.Length - 2] == "CP")
                                AddPotentialElement(AssociatedElement, Specific = MM_Database_CIM.LocateElement(splStr[1], "ShuntCompensator", splStr[splStr.Length - 1], ""));
                            else if (splStr[splStr.Length - 2] == "SVS")
                                AddPotentialElement(AssociatedElement, Specific = MM_Database_CIM.LocateElement(splStr[1], "StaticVarCompensator", splStr[splStr.Length - 1], ""));
                            else
                            {
                            }
                            /*
                            if (ElementType == MM_OneLine_Element.ElementTypeEnum.Line && (splStr[splStr.Length - 2] == "LN2" || splStr[splStr.Length - 2] == "ZBR2"))
                                AddPotentialElement(AssociatedElement, MM_Database_CIM.LocateElement(splStr[1], "Line", splStr[splStr.Length - 1], ""));
                            else if (ElementType == MM_OneLine_Element.ElementTypeEnum.Transformer && splStr[splStr.Length - 2] == "ND")
                            {
                                CIM_Element FoundNode = MM_Database_CIM.LocateElement(splStr[1], "ConnectivityNode", splStr[splStr.Length - 1], "");
                                if (FoundNode == null)
                                {
                                    Common.Types.MM_KVLevel KVLevel = MM_Overall_Display.FindKVLevel(OLComponent.Name.Split('_')[1]);
                                    CIM_Element FoundXF = MM_Database_CIM.LocateElement(splStr[1], "PowerTransformer", splStr[splStr.Length - 3], "");
                                    if (FoundXF != null)
                                        foreach (CIM_Element Winding in FoundXF.Nodes)
                                            if (MM_Overall_Display.FindKVLevel(Winding.VoltageLevel.Name) == KVLevel)
                                                AddPotentialElement(AssociatedElement, Winding);

                                }
                                else
                                    foreach (CIM_Element Elem in FoundNode.Elements)
                                        if (Elem.Type == "cim:TransformerWinding")
                                            AddPotentialElement(AssociatedElement, Elem);
                            }
                            else if (ElementType == MM_OneLine_Element.ElementTypeEnum.Bus && splStr[splStr.Length - 2] == "ND")
                                AddPotentialElement(AssociatedElement, MM_Database_CIM.LocateElement(splStr[1], "ConnectivityNode", splStr[splStr.Length - 1], ""));
                             */
                        }
                    }
                }

            //Now, go through the list and update accordingly

            if (AssociatedElement.Count > 1)
                if (Specific != null)
                {
                    if (Specific.Type == "cim:ACLineSegment")
                        Console.WriteLine("Overriding with specific line " + Specific["cim:Line>name"] + Specific.Name);
                    AssociatedElement.Clear();
                    AssociatedElement.Add(Specific);
                }
                else
                    for (int a = 1; a < AssociatedElement.Count; a++)
                        if (AssociatedElement[a] != AssociatedElement[a - 1])
                        {
                            MM_Log.LogEvent(new MM_Event_MultipleElements(OLComponent, AssociatedElement.ToArray()));
                            break;
                        }

            if (AssociatedElement.Count == 0)
                return null;
            else
            {
                OLComponent.AssociatedElement = AssociatedElement[0];
                return MM_Database_CIM.LocateElement(AssociatedElement[0], ExistingElements);
            }
        }


        /// <summary>
        /// Build linkages between elements and their adjacent ones
        /// </summary>
        /// <param name="OLComponents"></param>
        /// <returns></returns>
        public static Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> BuildLinkages(Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents)
        {
            //Find all pairs of adjacent elements, and link them
            Dictionary<MM_OneLine_XmlComponent, List<MM_OneLine_XmlComponent>> Linkages = new Dictionary<MM_OneLine_XmlComponent, List<MM_OneLine_XmlComponent>>();
            List<MM_OneLine_XmlComponent> Components = new List<MM_OneLine_XmlComponent>(OLComponents.Keys);
            for (int a = 0; a < Components.Count; a++)
                for (int b = a + 1; b < Components.Count; b++)
                {
                    if (CheckAdjacency(Components[a], Components[b]))
                    {
                        if (!Linkages.ContainsKey(Components[a]))
                            Linkages.Add(Components[a], new List<MM_OneLine_XmlComponent>());
                        if (!Linkages.ContainsKey(Components[b]))
                            Linkages.Add(Components[b], new List<MM_OneLine_XmlComponent>());
                        Linkages[Components[a]].Add(Components[b]);
                        Linkages[Components[b]].Add(Components[a]);
                    }
                }

            //Copy our list into an outgoing array
            Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Outgoing = new Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>(Linkages.Count);
            foreach (KeyValuePair<MM_OneLine_XmlComponent, List<MM_OneLine_XmlComponent>> kvp in Linkages)
                Outgoing.Add(kvp.Key, kvp.Value.ToArray());

            return Outgoing;
        }

        /// <summary>
        /// Determine whether two XML components are adjacent
        /// </summary>
        /// <param name="C1">The first component</param>
        /// <param name="C2">The second component</param>
        /// <returns></returns>
        private static bool CheckAdjacency(MM_OneLine_XmlComponent C1, MM_OneLine_XmlComponent C2)
        {
            //If we have a label, ignore it
            if (C1.Name.Contains("_LABEL") || C2.Name.Contains("_LABEL"))
                return false;

            //First, set it up so C1 is always the jumper 
            if (C2.Name.StartsWith("JUMPER_") && C1.Points.Length > 0)
                return CheckAdjacency(C2, C1);

            //If we have a jumper, test the endpoints to see if they hit
            else if (C1.Name.StartsWith("JUMPER_"))
            {

                foreach (Point pt in C2.Points)
                {
                    if (C1.Name.EndsWith("_H") && (Math.Min(Math.Abs(pt.X - C1.ElementRectangle.Left), Math.Abs(pt.X - C1.ElementRectangle.Right)) < 2 || (pt.X >= C1.ElementRectangle.Left && pt.X <= C1.ElementRectangle.Right)) && pt.Y >= C1.ElementRectangle.Top && pt.Y <= C1.ElementRectangle.Bottom)
                        return true;
                    else if (C1.Name.EndsWith("_V") && (Math.Min(Math.Abs(pt.Y - C1.ElementRectangle.Top), Math.Abs(pt.Y - C1.ElementRectangle.Bottom)) < 2 || (pt.Y >= C1.ElementRectangle.Top && pt.Y <= C1.ElementRectangle.Bottom)) && pt.X >= C1.ElementRectangle.Left && pt.X <= C1.ElementRectangle.Right)
                        return true;
                }
                return false;
            }

            //If we have a rectangle and a line, hit test the points.
            else if (C1.Points.Length == 0 && C2.Points.Length > 0)
                return LineWithinRectangle(C1.ElementRectangle, C2.Points);
            else if (C1.Points.Length > 0 && C2.Points.Length == 0)
                return LineWithinRectangle(C2.ElementRectangle, C1.Points);

            //Otherwise, if we have two lines, check each inflection point for matching
            else if (C1.Points.Length > 0 && C2.Points.Length > 0)
            {
                foreach (Point pt1 in C1.Points)
                    foreach (Point pt2 in C2.Points)
                        if (Math.Abs(pt1.X - pt2.X) < 2 && Math.Abs(pt1.Y - pt2.Y) < 2)
                            return true;
                return false;
            }

            //Otherwise, if we have two rectangles, check to see whether they intersect
            else
                return C1.ElementRectangle.IntersectsWith(C2.ElementRectangle) || C2.ElementRectangle.IntersectsWith(C1.ElementRectangle);
        }


        /// <summary>
        /// Determine whether a rectangle falls within a line
        /// </summary>
        /// <param name="BaseRect"></param>
        /// <param name="Points"></param>
        /// <returns></returns>
        private static bool LineWithinRectangle(Rectangle BaseRect, Point[] Points)
        {
            if (Points[0].X >= BaseRect.Left && Points[0].X <= BaseRect.Right && Points[0].Y >= BaseRect.Top && Points[0].Y <= BaseRect.Bottom)
                return true;
            for (int a = 1; a < Points.Length; a++)
            {
                bool InsideRectX = (Points[a - 1].X >= BaseRect.Left && Points[a].X <= BaseRect.Right) || (Points[a].X >= BaseRect.Left && Points[a - 1].X <= BaseRect.Right);
                bool OutsideRectX = (Points[a - 1].X <= BaseRect.Left && Points[a].X >= BaseRect.Right) || (Points[a].X <= BaseRect.Left && Points[a - 1].X >= BaseRect.Right);
                bool InsideRectY = (Points[a - 1].Y >= BaseRect.Top && Points[a].Y <= BaseRect.Bottom) || (Points[a].Y >= BaseRect.Top && Points[a - 1].Y <= BaseRect.Bottom);
                bool OutsideRectY = (Points[a - 1].Y <= BaseRect.Top && Points[a].Y >= BaseRect.Bottom) || (Points[a].Y <= BaseRect.Top && Points[a - 1].Y >= BaseRect.Bottom);
                if (InsideRectX && (InsideRectY || OutsideRectY))
                    return true;
                else if (InsideRectY && OutsideRectX)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Match the elements together based on connectivity and topology, and add nodes when possible
        /// </summary>
        /// <param name="Linkages"></param>
        /// <param name="Controls"></param>
        /// <param name="tTip"></param>
        /// <param name="DisplayParams"></param>
        /// <param name="SubElement"></param>
        /// <param name="OLComponents"></param>
        /// <param name="olView"></param>
        public static List<MM_OneLine_Node> RollUpNodes(Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Linkages, Control.ControlCollection Controls, ToolTip tTip, MM_OneLine_Display DisplayParams, CIM_Element SubElement, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents, MM_OneLine_Viewer olView)
        {
            List<MM_OneLine_Element> NewElems = new List<MM_OneLine_Element>();
            //Create our outgoing list of nodes
            int Removed = 0;
            
            
            List<MM_OneLine_Node> OutNodes = new List<MM_OneLine_Node>();
            List<MM_OneLine_Element> ToRemove = new List<MM_OneLine_Element>();
            Dictionary<MM_OneLine_XmlComponent, MM_Element> MatchElements = new Dictionary<MM_OneLine_XmlComponent, MM_Element>();
            Dictionary<MM_OneLine_XmlComponent, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>> Matches = new Dictionary<MM_OneLine_XmlComponent, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>>();
            List<MM_OneLine_PokePoint> Pokes = new List<MM_OneLine_PokePoint>();
            
            

            //Go through each node in the substation, and determine to what it's connected. Then, find the appropriate connections between the elements
            foreach (CIM_Element Node in SubElement.Nodes)
            {            
                //Build our MM representation of the node, and all elements with which it is connected
                MatchElements.Clear();
                Matches.Clear();
                MM_Node BaseNode = MM_Database_CIM.LocateElement(Node, olView.AddedElements) as MM_Node;
                MM_OneLine_Node FoundNode = null;

                    
                if (olView.DisplayElements.ContainsKey(BaseNode))
                    FoundNode = olView.DisplayElements[BaseNode] as MM_OneLine_Node;

                foreach (MM_Element Elem in BaseNode.ConnectedElements)
                {
                    MM_Element FindElem = Elem is MM_TransformerWinding ? (Elem as MM_TransformerWinding).Transformer : Elem;
                    if (olView.DisplayElements.ContainsKey(FindElem) && olView.DisplayElements[FindElem].Tag is MM_OneLine_XmlComponent)
                        MatchElements.Add(olView.DisplayElements[FindElem].Tag as MM_OneLine_XmlComponent, FindElem);
                }

                //Now, determine the linkages between the elements referenced
                foreach (KeyValuePair<MM_OneLine_XmlComponent, MM_Element> Component in MatchElements)
                {
                    Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> ConnectList = new Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>();
                    ConnectComponents(Linkages, Component.Key, ConnectList, new MM_OneLine_XmlComponent[] { });
                    foreach (KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> kvp in ConnectList)
                        if (MatchElements.ContainsKey(kvp.Key) || (FoundNode != null && (FoundNode.Tag as MM_OneLine_XmlComponent) == kvp.Key))
                        {
                            if (!Matches.ContainsKey(Component.Key))
                                Matches.Add(Component.Key, new Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>());
                            Matches[Component.Key].Add(kvp.Key, kvp.Value);
                        }
                }


                //If we have a proper match, go through and roll up the nodes
                if (MatchElements.Count != Matches.Count)
                    Console.WriteLine("Unable to find a match for {0} {1}.", BaseNode.ElemType.Name, BaseNode.Name);
                else
                {



                    //First, go through all connectivity nodes, to find the ones that are most commonly used, and tally line points if we need to average
                    if (FoundNode != null)
                    {
                        //Now that we have our node, go through all elements and pull through the node connections
                        foreach (KeyValuePair<MM_OneLine_XmlComponent, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>> kvp in Matches)
                            foreach (KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> kvp2 in kvp.Value)
                                BuildPathToTarget(FoundNode, kvp2.Key, OLComponents[kvp.Key], kvp2.Value, Matches, NewElems, ToRemove, olView, OLComponents);

                    }
                    else
                    {
                        Point PointTally = new Point();
                        int NumPoints = 0;
                        int MaxPoints = 0;
                        int MaxPointCount = 0;
                        Dictionary<MM_OneLine_XmlComponent, int> FoundPokes = new Dictionary<MM_OneLine_XmlComponent, int>();
                        Dictionary<Point, int> FoundPoints = new Dictionary<Point, int>();

                        //Try and find all poke points, first w/ nodes and lines, then also including jumpers
                        for (int a=0; a < 3; a++)
                            if (a == 0 || MaxPoints == 0)
                        foreach (KeyValuePair<MM_OneLine_XmlComponent, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>> kvp in Matches)
                            foreach (KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> kvp2 in kvp.Value)
                                foreach (MM_OneLine_XmlComponent ConnPoint in kvp2.Value)
                                    if (FoundNode == null && ConnPoint.Name.StartsWith("CONN_"))
                                        if (FoundPokes.ContainsKey(ConnPoint))
                                            MaxPoints = Math.Max(MaxPoints, (FoundPokes[ConnPoint] = FoundPokes[ConnPoint] + 1));
                                        else
                                        {
                                            FoundPokes.Add(ConnPoint, 1);
                                            MaxPoints = Math.Max(MaxPoints, 1);
                                        }
                                    else if (ConnPoint.Name.EndsWith("_LINES"))
                                    {
                                        //If we're on the last try with no matches, also look through all nodes - we may have a line w/ a node in the middle of it.
                                        if (a == 1)
                                            foreach (MM_OneLine_XmlComponent TestNode in OLComponents.Keys)
                                                if (TestNode.Name.StartsWith("CONN_") && LineWithinRectangle(TestNode.ElementRectangle, ConnPoint.Points))
                                                {
                                                    if (FoundPokes.ContainsKey(TestNode))
                                                        MaxPoints = Math.Max(MaxPoints, (FoundPokes[TestNode] = FoundPokes[TestNode] + 1));
                                                    else
                                                    {
                                                        FoundPokes.Add(TestNode, 1);
                                                        MaxPoints = Math.Max(MaxPoints, 1);
                                                    }
                                                    ToRemove.Add(OLComponents[TestNode]);
                                                }
                                        foreach (Point pt in ConnPoint.Points)
                                        {
                                            PointTally.X += pt.X;
                                            PointTally.Y += pt.Y;
                                            NumPoints++;

                                            //On our last-ditch effort, track the series of points, and see if we have any matches.
                                            //Also, go through all connection points, see if we can find a connection point running through this line
                                            if (a == 1)
                                            {
                                                Point pt2 = pt;
                                                foreach (Point pt3 in FoundPoints.Keys)
                                                    if (Math.Abs(pt2.X - pt3.X) <= 3 && Math.Abs(pt2.Y - pt3.Y) <= 3)
                                                        pt2 = pt3;

                                                if (FoundPoints.ContainsKey(pt2))
                                                    FoundPoints[pt2]++;
                                                else
                                                    FoundPoints.Add(pt2, 1);
                                                MaxPointCount = Math.Max(MaxPointCount, FoundPoints[pt2]);


                                            }
                                        }
                                    }
                                    else if (ConnPoint.Name.StartsWith("JUMPER_") && a == 2)
                                        if (FoundPokes.ContainsKey(ConnPoint))
                                            MaxPoints = Math.Max(MaxPoints, (FoundPokes[ConnPoint] = FoundPokes[ConnPoint] + 1));
                                        else
                                        {
                                            FoundPokes.Add(ConnPoint, 1);
                                            MaxPoints = Math.Max(MaxPoints, 1);
                                        }

                        

                        //If we have no connectivity nodes, just create a new one at the average inflection point
                        if (MaxPoints == 0 && NumPoints == 0)
                        { }
                        else if (MaxPoints == 0)
                        {
                            //In case we have the situation with a straight line and a node in the middle, check all CONN_*s that intersect with our point
                            Rectangle NodeRect = new Rectangle((PointTally.X / NumPoints) - 4, (PointTally.Y / NumPoints) - 4, 8, 8);
                            
                            //Try and pull in our most common point
                            if (MaxPointCount > Matches.Count)
                                foreach (KeyValuePair<Point, int> kvp in FoundPoints)
                                    if (kvp.Value == MaxPointCount)
                                        NodeRect = new Rectangle(kvp.Key.X - 4, kvp.Key.Y - 4, 8, 8);
                            
                            
                            
                            foreach (KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_Element> TestNodePoke in OLComponents)
                                if (TestNodePoke.Key.Name.StartsWith("CONN_") && TestNodePoke.Key.ElementRectangle.IntersectsWith(NodeRect))
                                    ToRemove.Add(TestNodePoke.Value);

                            FoundNode = new MM_OneLine_Node(BaseNode, DisplayParams, MM_OneLine_Element.enumOrientations.Unknown);
                            FoundNode.Bounds = NodeRect;
                            NewElems.Add(FoundNode);
                        }
                        else
                        {
                            foreach (KeyValuePair<MM_OneLine_XmlComponent, int> kvp in FoundPokes)
                                if (FoundNode == null && kvp.Value == MaxPoints)
                                {
                                    FoundNode = new MM_OneLine_Node(BaseNode, DisplayParams, MM_OneLine_Element.enumOrientations.Unknown);
                                    if (kvp.Key.Name.StartsWith("JUMPER"))
                                    {
                                        FoundNode.IsJumper = true;
                                        FoundNode.Orientation = kvp.Key.Name.EndsWith("_H") ? MM_OneLine_Element.enumOrientations.Horizontal : MM_OneLine_Element.enumOrientations.Vertical;
                                    }
                                    FoundNode.Bounds = kvp.Key.ElementRectangle;
                                    FoundNode.Tag = kvp.Key;
                                    NewElems.Add(FoundNode);
                                }
                        }
                    }

                    if (FoundNode != null)
                    {
                        //Now that we have our node, go through all elements and pull through the node connections
                        foreach (KeyValuePair<MM_OneLine_XmlComponent, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>> kvp in Matches)
                            foreach (KeyValuePair<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> kvp2 in kvp.Value)
                                BuildPathToTarget(FoundNode, kvp2.Key, OLComponents[kvp.Key], kvp2.Value, Matches, NewElems, ToRemove, olView, OLComponents);

                        //If we have a resouce node name available, make sure it's added
                        if (!String.IsNullOrEmpty(FoundNode.BaseNode.ResourceNode) && FoundNode.SecondaryDescriptor == null)
                        {
                            (FoundNode.SecondaryDescriptor = new MM_OneLine_SecondaryDescriptor(FoundNode, olView.DisplayParameters)).Location = new Point(FoundNode.Right, FoundNode.Top);
                            NewElems.Add(FoundNode.SecondaryDescriptor);
                        }


                        //Now, add our node to our outgoing list, and handle appropriately
                        OutNodes.Add(FoundNode);
                    }
                }                
            }
                
            
            //Make sure we've added in all our poke points
            foreach (MM_OneLine_Node NewNode in OutNodes)
                foreach (MM_OneLine_PokePoint[] PokeCollection in NewNode.ConnectionPoints.Values)
                    foreach (MM_OneLine_PokePoint Poke in PokeCollection)
                        if (!NewElems.Contains(Poke))
                            NewElems.Add(Poke);



            //Add our new nodes to the list of elements, remove consumed components, and add poke points
            DisplayParams.HandleRemovedElements(ToRemove.ToArray());
            DisplayParams.HandleAddedNodes(OutNodes.ToArray());
            DisplayParams.HandleAddedElements(NewElems.ToArray());

            MM_Log.LogEvent(new MacomberMap.Common.Events.MM_Event_Information(String.Format("Removed {0:#,##0} DDL elements, replaced them with {1:#,##0} nodes.", Removed, OutNodes.Count), typeof(MM_DDL_Importer)));                      

            return OutNodes;
        }

        /// <summary>
        /// Build a path from a node or busbar section to a target, based on a series of connecting points
        /// </summary>
        /// <param name="FoundNode"></param>
        /// <param name="NewElems"></param>
        /// <param name="Path"></param>
        /// <param name="TargetElement">The target element</param>
        /// <param name="TargetXml">The target element's XML</param>
        /// <param name="FullPath">The full path between the node and target element</param>
        /// <param name="ToRemove">The queue of items to be removed</param>        
        /// <param name="olView">The one line viewer</param>
        /// <param name="OLComponents">The collection of components and their corresponding one-line elements</param>
        private static void BuildPathToTarget(MM_OneLine_Node FoundNode, MM_OneLine_XmlComponent TargetXml, MM_OneLine_Element TargetElement, MM_OneLine_XmlComponent[] Path, Dictionary<MM_OneLine_XmlComponent, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>> FullPath, List<MM_OneLine_Element> NewElems, List<MM_OneLine_Element> ToRemove, MM_OneLine_Viewer olView, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents)
        {
            if (FoundNode.ConnectionPoints.ContainsKey(TargetElement))
                return;

            //If we have no path between our node and our element, simply add it in.
            if (Path.Length == 0)
            {
                FoundNode.ConnectionPoints.Add(TargetElement, new MM_OneLine_PokePoint[] { FoundNode });
                return;
            }

            
            //If we do have a path, try and locate our node point on it.
            int LastMatch = -1;
            for (int a = 0; a < 2; a++)
                if (a == 0 || LastMatch == -1)
                    for (int b = 0; b < Path.Length; b++)
                        if (Path[b].Name.StartsWith("CONN_") && Path[b].ElementRectangle.IntersectsWith(FoundNode.Bounds))
                            LastMatch = b;
                        else if (Path[b].Name.EndsWith("_LINES") && LineWithinRectangle(FoundNode.Bounds, Path[b].Points))
                            LastMatch = b;
                        else if (Path[b].Name.StartsWith("JUMPER_") && a == 1 && Path[b].ElementRectangle.IntersectsWith(FoundNode.Bounds))
                            LastMatch = b;
            

            //If we have a match, go from our target element backwards, adding in poke points as needed.
            MM_OneLine_Element LastElement = TargetElement;
            Stack<MM_OneLine_PokePoint> PokePath = new Stack<MM_OneLine_PokePoint>();                       
            //PokePath.Push(FoundNode);
            if (LastMatch != -1)
                for (int a = 1; a <= LastMatch; a++)
                //for (int a = Path.Length - 1; a >= LastMatch; a--)
                {
                    //If we have a series of inflection points, handle them appropriately
                    if (Path[a].Name.EndsWith("_LINES"))
                    {
                        bool LastIsClosest;                        
                        if (LastElement.Bounds.Contains(Path[a].Points[Path[a].Points.Length - 1]))                        
                            LastIsClosest = true;
                        else if (LastElement.Bounds.Contains(Path[a].Points[0]))
                            LastIsClosest = false;
                        else                        
                            LastIsClosest = (Math.Sqrt(Math.Pow(Path[a].Points[0].X - MM_OneLine_Element.CenterRect(LastElement.Bounds).X, 2) + Math.Pow(Path[a].Points[0].Y - MM_OneLine_Element.CenterRect(LastElement.Bounds).Y, 2)) < Math.Sqrt(Math.Pow(Path[a].Points[Path[a].Points.Length - 1].X - MM_OneLine_Element.CenterRect(LastElement.Bounds).X, 2) + Math.Pow(Path[a].Points[Path[a].Points.Length - 1].Y - MM_OneLine_Element.CenterRect(LastElement.Bounds).Y, 2)));


                        //If we have a partial match (e.g., a node in the middle of the line), use our special logic
                        if (LineWithinRectangle(FoundNode.Bounds, Path[a].Points))
                        {
                        }
                        else if (LastIsClosest)
                        {
                            //if (Path[a-1].Name.EndsWith("_LINES"))
                            //    PokePath.Push((LastElement = olView.LocatePoke(new Rectangle(Path[a].Points[Path[a].Points.Length-1].X - 2, Path[a].Points[Path[a].Points.Length-1].Y - 2, 4, 4), FoundNode, olView.DisplayParameters, FoundNode.BaseElement, MM_OneLine_Element.enumOrientations.Unknown, false, false)) as MM_OneLine_PokePoint);
                            for (int b = Path[a].Points.Length - 2; b >= 0; b--)
                                PokePath.Push((LastElement = olView.LocatePoke(new Rectangle(Path[a].Points[b].X - 2, Path[a].Points[b].Y - 2, 4, 4), FoundNode, olView.DisplayParameters, FoundNode.BaseElement, MM_OneLine_Element.enumOrientations.Unknown, false, false)) as MM_OneLine_PokePoint);
                        }
                        else
                        {
                            //if (Path[a + 1].Name.EndsWith("_LINES"))
                            //    PokePath.Add((LastElement = olView.LocatePoke(new Rectangle(Path[a].Points[0].X - 2, Path[a].Points[0].Y - 2, 4, 4), FoundNode, olView.DisplayParameters, FoundNode.BaseElement, MM_OneLine_Element.enumOrientations.Unknown, false, false)) as MM_OneLine_PokePoint);
                            for (int b = 1; b < Path[a].Points.Length; b++)
                                PokePath.Push((LastElement = olView.LocatePoke(new Rectangle(Path[a].Points[b].X - 2, Path[a].Points[b].Y - 2, 4, 4), FoundNode, olView.DisplayParameters, FoundNode.BaseElement, MM_OneLine_Element.enumOrientations.Unknown, false, false)) as MM_OneLine_PokePoint);
                        }
                                                                                              
                    }
                    else if (Path[a].Name.StartsWith("JUMPER_"))
                        if (Path[a].ElementRectangle.IntersectsWith(FoundNode.Bounds))
                            PokePath.Push((LastElement = FoundNode) as MM_OneLine_PokePoint);
                        else
                            PokePath.Push((LastElement = olView.LocatePoke(Path[a].ElementRectangle, FoundNode, olView.DisplayParameters, FoundNode.BaseElement, Path[a].Name.EndsWith("_H") ? MM_OneLine_Element.enumOrientations.Horizontal : MM_OneLine_Element.enumOrientations.Vertical, true, true)) as MM_OneLine_PokePoint);
                    else if (Path[a].Name.StartsWith("CONN_"))
                        if (Path[a].ElementRectangle.IntersectsWith(FoundNode.Bounds))
                            PokePath.Push((LastElement = FoundNode) as MM_OneLine_PokePoint);
                        else
                            PokePath.Push((LastElement = olView.LocatePoke(Path[a].ElementRectangle, FoundNode, olView.DisplayParameters, FoundNode.BaseElement, MM_OneLine_Element.enumOrientations.Unknown, false, true)) as MM_OneLine_PokePoint);
                    else
                        Console.WriteLine("Unknown element in chain: " + Path[a].Name);
                    if (OLComponents[Path[a]] is MM_OneLine_UnlinkedElement)
                        ToRemove.Add(OLComponents[Path[a]]);

                }
            else
                Console.WriteLine("Unable to locate a match point from node {0} to {1} {2}", FoundNode.BaseElement.Name, TargetElement.BaseElement.ElemType.Name, TargetElement.BaseElement.Name);

            PokePath.Push(FoundNode);
            
            //Now, remove any unwanted invisible poke points
            List<MM_OneLine_PokePoint> NodePokes = new List<MM_OneLine_PokePoint>(PokePath);

            for (int a = 1; a < NodePokes.Count; a++)
                if (!NodePokes[a].IsVisible)
                {
                    Point CenterLast = MM_OneLine_Element.CenterRect(NodePokes[a - 1].Bounds);
                    Point CenterThis = MM_OneLine_Element.CenterRect(NodePokes[a].Bounds);
                    Point CenterNext;
                    if (a < NodePokes.Count - 1)
                        CenterNext = MM_OneLine_Element.CenterRect(NodePokes[a + 1].Bounds);
                    else
                        CenterNext = MM_OneLine_Element.CenterRect(TargetElement.Bounds);


                    //Determine the angles between the two
                    double AngleLast = 180.0 * Math.Atan2(CenterThis.Y - CenterLast.Y, CenterThis.X - CenterLast.X) / Math.PI;
                    double AngleNext = 180.0 * Math.Atan2(CenterNext.Y - CenterThis.Y, CenterNext.X - CenterThis.X) / Math.PI;

                    //If we find one with a angle difference < 45, remove it.
                    if (Math.Abs(AngleLast - AngleNext) < 45.0)
                    {
                        olView.PanelControls.Remove(NodePokes[a]);
                        NodePokes.RemoveAt(a--);
                    }
                }

            /*for (int a = 0; a < NodePokes.Count; a++)                
            {
                NodePokes[a].Left -= olView.PanelScrollPosition.X;
                NodePokes[a].Top -= olView.PanelScrollPosition.Y;
            }*/
            if (!FoundNode.ConnectionPoints.ContainsKey(TargetElement))
                FoundNode.ConnectionPoints.Add(TargetElement, NodePokes.ToArray());   
        }

        /// <summary>
        /// Locate a node based on its components
        /// </summary>
        /// <param name="OLComponent">The one-line component for the node</param>
        /// <param name="OLComponents">The range of one-line components</param>
        /// <param name="Linkages">The element linkages</param>
        /// <param name="OutList">The list of paths to the node</param>
        /// <returns></returns>        
        public static CIM_Element LocateNode(MM_OneLine_XmlComponent OLComponent, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Linkages, out Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> OutList)
        {
            //Connect this component to all adjacent components
            OutList = new Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]>();
            ConnectComponents(Linkages, OLComponent, OutList, new MM_OneLine_XmlComponent[0]);

            //Now, build a list of all touching elements' nodes
            Dictionary<CIM_Element, int> TrackNodes = new Dictionary<CIM_Element, int>(OutList.Count);
            foreach (MM_OneLine_XmlComponent Component in OutList.Keys)
                if (Component.AssociatedElement.Type == "cim:ConnectivityNode")
                    foreach (CIM_Element Node in Component.AssociatedElement.Elements)
                        if (TrackNodes.ContainsKey(Node))
                            TrackNodes[Node]++;
                        else
                            TrackNodes.Add(Node, 1);
                else if (Component.AssociatedElement.Type == "cim:PowerTransformer")
                    foreach (CIM_Element Winding in Component.AssociatedElement.Nodes)
                        foreach (CIM_Element Node in Winding.Nodes)
                            if (TrackNodes.ContainsKey(Node))
                                TrackNodes[Node]++;
                            else
                                TrackNodes.Add(Node, 1);
                else
                    foreach (CIM_Element Node in Component.AssociatedElement.Nodes)
                        if (TrackNodes.ContainsKey(Node))
                            TrackNodes[Node]++;
                        else
                            TrackNodes.Add(Node, 1);

            //Then, determine which one has the proper element count
            bool FoundMatch = false;
            CIM_Element OutNode = null;
            foreach (KeyValuePair<CIM_Element, int> kvp in TrackNodes)
                if (kvp.Key.Type != "cim:PowerTransformer" && kvp.Value == OutList.Count)
                {
                    if (FoundMatch)
                        Console.WriteLine("Duplicate match for " + OLComponent.ToString() + ": " + OutNode.ToString() + " and " + kvp.Key.ToString());
                    FoundMatch = true;
                    OutNode = kvp.Key;
                }


            return OutNode;
        }


        

        /// <summary>
        /// Add a poke point to the collection
        /// </summary>
        /// <param name="OLComponent">The one-line component</param>
        /// <param name="Node">The CIM node</param>
        /// <param name="Pokes">The collection of poke points</param>
        /// <param name="NewPoint">The new poke point control</param>
        /// <param name="OLView">The one-line viewer</param>
        private static MM_OneLine_PokePoint AddPoke(MM_OneLine_XmlComponent OLComponent, CIM_Element Node, List<MM_OneLine_PokePoint> Pokes, MM_OneLine_PokePoint NewPoint, MM_OneLine_Viewer OLView)
        {
            //NewPoint.Tag = OLComponent;
            //OLComponent.AssociatedElement = Node;
            Pokes.Add(NewPoint);
            OLView.DisplayParameters.HandleAddedElements(new MM_OneLine_Element[] { NewPoint });            
            return NewPoint;
        }


        /// <summary>
        /// Connect a component to other compontents, based on a list of elements
        /// </summary>
        /// <param name="Linkages">The linkages between elements</param>
        /// <param name="Component">The component around which the parsing should occur</param>
        /// <param name="OutList">The outgoing list</param>
        /// <param name="SeenElements">The elements already seen by the testing</param>
        public static void ConnectComponents(Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Linkages, MM_OneLine_XmlComponent Component, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> OutList, MM_OneLine_XmlComponent[] SeenElements)
        { 
            //Prevent us from going crazy
            if (SeenElements.Length > 20)
                throw new InvalidOperationException("Too many elements to traverse this path - likely traversing unlinked elements."); ;
            List<MM_OneLine_XmlComponent> OutSeen = new List<MM_OneLine_XmlComponent>(SeenElements);            
            OutSeen.Add(Component);

            //Now, parse around our element, and act recursively
            if (Linkages.ContainsKey(Component))
                foreach (MM_OneLine_XmlComponent NextComponent in Linkages[Component])
                    if (NextComponent.Parent == null)
                        if (Array.IndexOf(SeenElements, NextComponent) == -1 && !OutList.ContainsKey(NextComponent))
                            if (NextComponent.AssociatedElement != null)
                            {
                                //Pull in additional nodes if present
                                /*if (Component.Name.EndsWith("_LINES"))
                                    if (OutList.ContainsKey(Component))
                                        foreach (MM_OneLine_XmlComponent Component2 in OutList[Component])
                                            if (Component2.Name.StartsWith("CONN_"))
                                                OutSeen.Add(Component2);*/
                                OutList.Add(NextComponent, OutSeen.ToArray());
                            }
                            else
                                ConnectComponents(Linkages, NextComponent, OutList, OutSeen.ToArray());
        }


        /*
        /// <summary>
        /// Connect a component to other compontents, based on a list of elements
        /// </summary>
        /// <param name="Linkages">The linkages between elements</param>
        /// <param name="Component">The component around which the parsing should occur</param>
        /// <param name="OutList">The outgoing list</param>
        /// <param name="SeenElements">The elements already seen by the testing</param>
        public static void ConnectComponents(Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Linkages, MM_OneLine_XmlComponent Component, List<MM_OneLine_XmlComponent> OutList, List<MM_OneLine_XmlComponent> SeenElements)
        {
            SeenElements.Add(Component);

            //Console.WriteLine("Looking around {0}", Component);
            //First, add our current element to the seen list.            
            //Now, parse around our element, and act recursively
            if (Linkages.ContainsKey(Component))
                foreach (MM_OneLine_XmlComponent NextComponent in Linkages[Component])
                    if (!SeenElements.Contains(NextComponent) && !OutList.Contains(NextComponent))
                        if (NextComponent.AssociatedElement != null)
                            OutList.Add(NextComponent);
                        else
                            ConnectComponents(Linkages, NextComponent, OutList, SeenElements);
        }*/




        /// <summary>
        /// Build our connections from one XML component to all other elements we can touch
        /// </summary>
        /// <param name="BaseComponent"></param>
        /// <param name="Linkages"></param>
        /// <returns></returns>
        private static Dictionary<MM_OneLine_XmlComponent, Point[]> BuildConnections(MM_OneLine_XmlComponent BaseComponent, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Linkages)
        {
            Dictionary<MM_OneLine_XmlComponent, Point[]> OutVal = new Dictionary<MM_OneLine_XmlComponent, Point[]>(10);
            //Recursively go through all linkages under the present, and link up.            
            WalkTheLine(OutVal, Linkages, BaseComponent, new MM_OneLine_XmlComponent[] { BaseComponent });
            return OutVal;
        }


        /// <summary>
        /// Recursively walk through a line until we hit an element with identification. When we do, add the full path of points between the two
        /// </summary>
        /// <param name="OutVal">The outgoing dictionary</param>
        /// <param name="SeenComponents">The seen components</param>
        /// <param name="BaseComponent"></param>
        /// <param name="Linkages"></param>
        /// <returns></returns>
        private static void WalkTheLine(Dictionary<MM_OneLine_XmlComponent, Point[]> OutVal, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_XmlComponent[]> Linkages, MM_OneLine_XmlComponent BaseComponent, MM_OneLine_XmlComponent[] SeenComponents)
        {

            foreach (MM_OneLine_XmlComponent Component in Linkages[BaseComponent])
                if (Array.IndexOf(SeenComponents, Component) == -1)

                    if (Component.AssociatedElement != null)
                    {
                        List<Point> PointList = new List<Point>();
                        foreach (MM_OneLine_XmlComponent XmlLine in SeenComponents)
                            PointList.AddRange(XmlLine.Points);
                        if (!OutVal.ContainsKey(Component))
                            OutVal.Add(Component, PointList.ToArray());
                    }
                    else
                    {
                        MM_OneLine_XmlComponent[] OutComponents = new MM_OneLine_XmlComponent[SeenComponents.Length + 1];
                        SeenComponents.CopyTo(OutComponents, 0);
                        OutComponents[SeenComponents.Length] = Component;
                        WalkTheLine(OutVal, Linkages, Component, OutComponents);
                    }
        }

        /// <summary>
        /// Determine whether a point lies within a rectangle's plane
        /// </summary>
        /// <param name="Rect">The rectangle to compare</param>
        /// <param name="pt">The point to compare</param>
        /// <returns></returns>
        private static bool IntersectsRectangleY(Rectangle Rect, Point pt)
        {
            return pt.Y >= Rect.Top && pt.Y <= Rect.Bottom;
        }

        private static bool IntersectsRectangleX(Rectangle Rect, Point pt)
        {
            return pt.X >= Rect.Left && pt.X <= Rect.Right;
        }


        /// <summary>
        /// Find all elements surrounding a component
        /// </summary>
        /// <param name="BaseComponent"></param>
        /// <param name="OLComponents">The one-line components</param>
        /// <returns></returns>
        private static MM_OneLine_XmlComponent[] SurroundingElements(MM_OneLine_XmlComponent BaseComponent, List<MM_OneLine_XmlComponent> OLComponents)
        {
            List<MM_OneLine_XmlComponent> OutComponents = new List<MM_OneLine_XmlComponent>();

            //Search through all elements, to find lines that touch on our requested element
            Rectangle BaseRect = BaseComponent.ElementRectangle;
            if (BaseComponent.Name.StartsWith("JUMPER_") && BaseComponent.Name.EndsWith("_H"))
            {
                foreach (MM_OneLine_XmlComponent Component in OLComponents)
                    foreach (Point pt in Component.Points)
                        if (IntersectsRectangleX(BaseRect, pt) && !IntersectsRectangleY(BaseRect, pt))
                            OutComponents.Add(Component);
            }
            else if (BaseComponent.Name.StartsWith("JUMPER_") && BaseComponent.Name.EndsWith("_V"))
            {
                foreach (MM_OneLine_XmlComponent Component in OLComponents)
                    foreach (Point pt in Component.Points)
                        if (IntersectsRectangleY(BaseRect, pt) && !IntersectsRectangleX(BaseRect, pt))
                            OutComponents.Add(Component);
            }
            else
            {
                foreach (MM_OneLine_XmlComponent Component in OLComponents)
                    if (Component.Name.StartsWith("JUMPER_") && Component.Name.EndsWith("_H"))
                    {
                        foreach (Point pt in BaseComponent.Points)
                            if (IntersectsRectangleX(Component.ElementRectangle, pt) && !IntersectsRectangleY(Component.ElementRectangle, pt))
                                OutComponents.Add(Component);
                    }
                    else if (Component.Name.StartsWith("JUMPER_") && Component.Name.EndsWith("_V"))
                    {
                        foreach (Point pt in BaseComponent.Points)
                            if (!IntersectsRectangleX(Component.ElementRectangle, pt) && IntersectsRectangleY(Component.ElementRectangle, pt))
                                OutComponents.Add(Component);
                    }
                    else if (Component.Points.Length > 0)
                    {
                        foreach (Point pt in Component.Points)
                            if (IntersectsRectangleX(BaseRect, pt) && IntersectsRectangleY(BaseRect, pt) && !OutComponents.Contains(Component))
                                OutComponents.Add(Component);
                    }
                    else if (BaseComponent.Points.Length > 0)
                    {
                        foreach (Point pt in BaseComponent.Points)
                            if (IntersectsRectangleX(Component.ElementRectangle, pt) && IntersectsRectangleY(Component.ElementRectangle, pt) && !OutComponents.Contains(Component))
                                OutComponents.Add(Component);
                    }
            }
            return OutComponents.ToArray();
        }

        /// <summary>
        /// Go through our list of transformers, to build adjacent ones into single elements
        /// </summary>
        /// <param name="OLComponents">The one-line components</param>
        /// <param name="Transformers">The integrated transformers</param>
        public static void BuildTransformers(Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents, List<MM_OneLine_XmlComponent> Transformers)
        {
            bool FoundWinding = false;
            do
            {
                FoundWinding = false;
                foreach (MM_OneLine_XmlComponent Winding in OLComponents.Keys)
                    if (Winding.Name.StartsWith("TRANS_PHASE_") && Winding.Name.Contains("KV_"))
                    {
                        if (Winding.SubElements == null)
                            Winding.SubElements = new List<MM_OneLine_XmlComponent>(4);                        
                    }
                    else if (Winding.Name.StartsWith("TRANS_") && Winding.Name.Contains("KV_"))
                    {
                        //First, locate our transformer that will contain this one.
                        MM_OneLine_XmlComponent XF = null;
                        foreach (MM_OneLine_XmlComponent Trans in Transformers)
                            if (CheckTransformer(Trans.ElementRectangle, Winding.ElementRectangle))
                            {
                                XF = Trans;
                                XF.SubElements.Add(Winding);
                                XF.ElementRectangle = Rectangle.FromLTRB(Math.Min(XF.ElementRectangle.Left, Winding.ElementRectangle.Left), Math.Min(XF.ElementRectangle.Top, Winding.ElementRectangle.Top), Math.Max(XF.ElementRectangle.Right, Winding.ElementRectangle.Right), Math.Max(XF.ElementRectangle.Bottom, Winding.ElementRectangle.Bottom));
                            }

                        //If we have no transformer, create one
                        if (XF == null)
                        {
                            XF = new MM_OneLine_XmlComponent(Winding);
                            XF.SubElements = new List<MM_OneLine_XmlComponent>(5);
                            XF.SubElements.Add(Winding);
                            Transformers.Add(XF);
                        }

                        //Flag our transformer as being found, change any linkages, and restart the loop
                        OLComponents.Remove(Winding);
                        foreach (MM_OneLine_XmlComponent Component in OLComponents.Keys)
                            if (Component.SubElements != null)
                                for (int a = 0; a < Component.SubElements.Count; a++)
                                    if (Component.SubElements[a] == Winding)
                                        Component.SubElements[a] = XF;

                        FoundWinding = true;
                        break;

                    }
            } while (FoundWinding);
        }

        /// <summary>
        /// Check to see if two transformer winding rectangles are close
        /// </summary>
        /// <param name="XF">The transfomer rectangle</param>
        /// <param name="Winding">The winding rectangle</param>
        /// <returns></returns>
        private static bool CheckTransformer(Rectangle XF, Rectangle Winding)
        {
            double Dist, Angle, DeltaX, DeltaY;
            bool WithinX, WithinY;
            MM_OneLine_TuningParameter.ComputeDistanceAndAngle(XF, Winding, out Dist, out Angle, out DeltaX, out DeltaY, out WithinX, out WithinY);
            return Math.Abs(DeltaX) <= 5 && Math.Abs(DeltaY) <= 5;
        }


        

        /// <summary>
        /// Roll up descriptor elements, removing them as appropriate
        /// </summary>
        /// <param name="olView">The one-line viewer</param>
        /// <param name="OLComponents">The collection of one-line components</param>
        public static void RollUpDescriptors(MM_OneLine_Viewer olView, Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents)
        {
            //First, build our list of unlinked elements
            int Descriptors = 0;
            Dictionary<MM_OneLine_XmlComponent, MM_OneLine_UnlinkedElement> Components = new Dictionary<MM_OneLine_XmlComponent, MM_OneLine_UnlinkedElement>();
            foreach (Control ctl in olView.PanelControls)
                if (ctl is MM_OneLine_UnlinkedElement && ctl.Tag is MM_OneLine_XmlComponent)
                    if (!Components.ContainsKey(ctl.Tag as MM_OneLine_XmlComponent))
                        Components.Add(ctl.Tag as MM_OneLine_XmlComponent, ctl as MM_OneLine_UnlinkedElement);

      
            //Now, go through our display elements and roll them up
            List<MM_OneLine_XmlComponent> Labels = new List<MM_OneLine_XmlComponent>();
            List<MM_OneLine_Element> ToRemove = new List<MM_OneLine_Element>();
            List<MM_OneLine_Element> ToAdd = new List<MM_OneLine_Element>();

            foreach (Control ctl in olView.PanelControls)
                if (ctl is MM_OneLine_Element && (ctl is MM_OneLine_Descriptor == false) && ctl.Tag is MM_OneLine_XmlComponent && (ctl.Tag as MM_OneLine_XmlComponent).SubElements != null && (ctl.Tag as MM_OneLine_XmlComponent).SubElements.Count > 0)
                {
                    //First, build our list of all labels, whether connected to the transformer windings or element themselves
                    MM_OneLine_XmlComponent Component = ctl.Tag as MM_OneLine_XmlComponent;
                    Labels.Clear();
                    if (Component.Name.StartsWith("TRANS_"))
                    {
                        foreach (MM_OneLine_XmlComponent Winding in Component.SubElements)
                            if (Winding.SubElements != null)
                                foreach (MM_OneLine_XmlComponent Label in Winding.SubElements)
                                    Labels.Add(Label);
                    }
                    else
                        Labels.AddRange(Component.SubElements);


                    if (Labels.Count > 0)
                        Component.DescriptorRectangle = Labels[0].ElementRectangle;
                    else
                        Component.DescriptorRectangle = new Rectangle(ctl.Left + olView.PanelScrollPosition.X, ctl.Bottom + 2 + olView.PanelScrollPosition.Y, ctl.Width, ctl.Height);
                    foreach (MM_OneLine_XmlComponent SubComponent in Labels)
                    {
                        Component.DescriptorRectangle = Rectangle.FromLTRB(Math.Min(Component.DescriptorRectangle.Left, SubComponent.ElementRectangle.Left), Math.Min(Component.DescriptorRectangle.Top, SubComponent.ElementRectangle.Top), Math.Max(Component.DescriptorRectangle.Right, SubComponent.ElementRectangle.Right), Math.Max(Component.DescriptorRectangle.Bottom, SubComponent.ElementRectangle.Bottom));
                        OLComponents.Remove(SubComponent);
                        if (Components.ContainsKey(SubComponent))
                            ToRemove.Add(Components[SubComponent]);
                    }

                    //Now, create the descriptor
                    MM_OneLine_Descriptor NewDesc = new MM_OneLine_Descriptor(ctl as MM_OneLine_Element, olView.DisplayParameters);
                    Descriptors++;
                    (ctl as MM_OneLine_Element).Descriptor = NewDesc;
                    ToAdd.Add(NewDesc);
                    NewDesc.Bounds = Component.DescriptorRectangle;
                    NewDesc.Width += 4;
                    NewDesc.Left += olView.PanelScrollPosition.X;
                    NewDesc.Top += olView.PanelScrollPosition.Y;
                    NewDesc.ForeColor = Color.Gray;
                    //Now, go through our label XML to locate any information
                    String FontFamily = null;
                    String FontSize = null;
                    foreach (MM_OneLine_XmlComponent SubComponent in Labels)
                    {
                        foreach (XmlNode AttrNode in SubComponent.BaseElement.SelectNodes("*/@font_family"))
                            FontFamily = AttrNode.Value;
                        foreach (XmlNode AttrNode in SubComponent.BaseElement.SelectNodes("*/@font_size"))
                            FontSize = AttrNode.Value;
                    }

                    if (!string.IsNullOrEmpty(FontFamily) && !string.IsNullOrEmpty(FontSize))
                        NewDesc.Font = new Font(FontFamily, float.Parse(FontSize) - 3f);

                   

                    //If we don't have a large busbar section, try and position it appropriately.

                    if (NewDesc.ParentElement is MM_OneLine_Node && Math.Max(NewDesc.ParentElement.Height, NewDesc.ParentElement.Width) > 75)
                        NewDesc.RecomputeSize();
                    else
                    {
                        //Determine our centers and prepare to receive orientation
                        MM_OneLine_Element.enumOrientations Orientation = MM_OneLine_Element.enumOrientations.Unknown;
                        Point CenterDesc = MM_OneLine_Element.CenterRect(NewDesc.DisplayRectangle);
                        Point CenterParent = MM_OneLine_Element.CenterRect(NewDesc.ParentElement.Bounds);



                        int Buffer = 2;
                        //If our descriptor is within the parent vertically, determine whether it's to the right
                        if (NewDesc.Top > NewDesc.ParentElement.Top && NewDesc.Bottom < NewDesc.ParentElement.Bottom && NewDesc.Right > NewDesc.ParentElement.Right)
                            Orientation = MM_OneLine_Element.enumOrientations.Right;

                        else if (NewDesc.Left > NewDesc.ParentElement.Left && NewDesc.Right < NewDesc.ParentElement.Right && NewDesc.Bottom > NewDesc.ParentElement.Bottom)
                            Orientation = MM_OneLine_Element.enumOrientations.Down;

                        //If our element is to the right, we know that has to be it (it may have been sized down)                                                                      
                        else if (NewDesc.Left >= NewDesc.ParentElement.Right - Buffer)
                            Orientation = MM_OneLine_Element.enumOrientations.Right;

                        //Or if it's flanking top to bottom and right of the center
                        else if (NewDesc.Top < NewDesc.ParentElement.Top && NewDesc.Bottom > NewDesc.ParentElement.Bottom && NewDesc.Left > NewDesc.ParentElement.Left + (NewDesc.ParentElement.Width / 2))
                            Orientation = MM_OneLine_Element.enumOrientations.Right;

                        //If not, and our element is below, we know that's the case
                        else if (NewDesc.Top >= NewDesc.ParentElement.Bottom - Buffer)
                            Orientation = MM_OneLine_Element.enumOrientations.Down;

                        //Or if it's flanking left to right and below the center
                        else if (NewDesc.Left < NewDesc.ParentElement.Left && NewDesc.Right > NewDesc.ParentElement.Right && NewDesc.Top > NewDesc.ParentElement.Top + (NewDesc.ParentElement.Height / 2))
                            Orientation = MM_OneLine_Element.enumOrientations.Down;

                        //If we're to the left, handle that
                        else if (NewDesc.Right - Buffer <= NewDesc.ParentElement.Left)
                            Orientation = MM_OneLine_Element.enumOrientations.Left;

                        //Or above
                        else if (NewDesc.Bottom - Buffer <= NewDesc.ParentElement.Top)
                            Orientation = MM_OneLine_Element.enumOrientations.Up;

                        else if (NewDesc.Left > NewDesc.ParentElement.Left && NewDesc.Top >= NewDesc.ParentElement.Top && NewDesc.Top < NewDesc.ParentElement.Top + (NewDesc.ParentElement.Height / 2))
                            Orientation = MM_OneLine_Element.enumOrientations.Right;

                        else if (NewDesc.Right > NewDesc.ParentElement.Left && NewDesc.Top >= NewDesc.ParentElement.Top && NewDesc.Left < NewDesc.ParentElement.Left)
                            Orientation = MM_OneLine_Element.enumOrientations.Left;

                        else if (NewDesc.Top < NewDesc.ParentElement.Bottom & NewDesc.Top >= NewDesc.ParentElement.Top + (NewDesc.ParentElement.Height / 2) && NewDesc.Left > NewDesc.ParentElement.Left && NewDesc.Left <= NewDesc.ParentElement.Right)
                            Orientation = MM_OneLine_Element.enumOrientations.Down;
                        /*
                        using (Bitmap OutImage = new Bitmap(Math.Max(NewDesc.Right, NewDesc.ParentElement.Right) + 5, Math.Max(NewDesc.Bottom, NewDesc.ParentElement.Bottom) + 5))
                        using (Graphics g = Graphics.FromImage(OutImage))
                        {

                            g.DrawRectangle(Pens.DarkGreen, NewDesc.ParentElement.Left + olView.PanelScrollPosition.X, NewDesc.ParentElement.Top + olView.PanelScrollPosition.Y, NewDesc.ParentElement.Width, NewDesc.ParentElement.Height);
                            g.DrawRectangle(Pens.LightBlue, NewDesc.Left + olView.PanelScrollPosition.X, NewDesc.Top + olView.PanelScrollPosition.Y, NewDesc.Width, NewDesc.Height);
                            g.DrawString(NewDesc.ImageText, NewDesc.Font, Brushes.White, new Rectangle(NewDesc.Left + olView.PanelScrollPosition.X, NewDesc.Top + olView.PanelScrollPosition.Y, NewDesc.Width, NewDesc.Height), MM_OneLine_Element.CenterFormat);
                            StringBuilder OutString = new StringBuilder();
                            OutString.AppendLine(String.Format("Comparing element ({0},{1})-({2},{3}) and descriptor ({4},{5})-({6},{7})", NewDesc.ParentElement.Left, NewDesc.ParentElement.Top, NewDesc.ParentElement.Right, NewDesc.ParentElement.Bottom, NewDesc.Left, NewDesc.Top, NewDesc.Right, NewDesc.Bottom));
                            OutString.AppendLine("Orientation: " + Orientation.ToString());
                            //foreach (BoundDistance dist in Bounds)
                            //    OutString.AppendLine(String.Format("{0}:{1} deg, {2} dist", dist.Orientation, dist.Angle.ToString("0"), dist.Distance.ToString("#,##0.00")));
                            g.DrawString(OutString.ToString(), SystemFonts.DefaultFont, Brushes.White, 2, 2);
                            OutImage.Save(@"C:\Documents and Settings\mlegatt\Desktop\imagetest\" + Orientation.ToString() + " - " + NewDesc.ParentElement.BaseElement.ElemType.Name + " " + NewDesc.ParentElement.BaseElement.Name + ".jpg", System.Drawing.Imaging.ImageFormat.Jpeg);
                        }*/



                        //Now, update our positioning.
                        NewDesc.RecomputeSize();
                        CenterDesc = MM_OneLine_Element.CenterRect(NewDesc.DisplayRectangle);
                        if (Orientation == MM_OneLine_Element.enumOrientations.Right)
                            NewDesc.Location = new Point(NewDesc.ParentElement.Right + 2, CenterParent.Y - CenterDesc.Y);
                        else if (Orientation == MM_OneLine_Element.enumOrientations.Down)
                            NewDesc.Location = new Point(CenterParent.X - CenterDesc.X, NewDesc.ParentElement.Bottom + 2);
                        else if (Orientation == MM_OneLine_Element.enumOrientations.Left)
                            NewDesc.Location = new Point(NewDesc.ParentElement.Left - NewDesc.Width - 2, CenterParent.Y - CenterDesc.Y);
                        else if (Orientation == MM_OneLine_Element.enumOrientations.Up)
                            NewDesc.Location = new Point(CenterParent.X - CenterDesc.X, NewDesc.ParentElement.Top - NewDesc.Height - 7);



                    }

                }
            olView.DisplayParameters.HandleRemovedElements(ToRemove.ToArray());
            olView.DisplayParameters.HandleAddedElements(ToAdd.ToArray());
            MM_Log.LogEvent(new MM_Event_Information(String.Format("Rolled up {0:#,##0} descriptors for DDL.", Descriptors), typeof(MM_DDL_Importer)));
        }       
      
        /// <summary>
        /// Go through the pricing vectors for the substation, and add them as appropriate
        /// </summary>
        /// <param name="OLComponents"></param>
        /// <param name="olView"></param>
        public static List<MM_OneLine_PricingVector> BuildPricingVectors(Dictionary<MM_OneLine_XmlComponent, MM_OneLine_Element> OLComponents, MM_OneLine_Viewer olView)
        {

            List<MM_OneLine_PricingVector> Vectors = new List<MM_OneLine_PricingVector>();

            foreach (CIM_Element Elem in MM_Database_CIM.TEIDs[olView.BaseElement.TEID].Elements)
                if (Elem.Type == "etx:PricingVector")
                {
                    //Retrieve our pricing vector
                    MM_PricingVector PV = MM_Database_CIM.LocateElement(Elem, olView.AddedElements) as MM_PricingVector;

                    if (olView.DisplayNodes.ContainsKey(PV.NodeElement))
                    {
                        //Find our node and other element
                        MM_OneLine_Node FoundNodeElem = olView.DisplayNodes[PV.NodeElement];
                        MM_OneLine_Element OtherElement;
                        if (PV.OtherElement.ElemType.Name == "TransformerWinding")
                            OtherElement = olView.TransformerWindings[PV.OtherElement];
                        else
                            OtherElement = olView.DisplayElements[PV.OtherElement];

                        if (!FoundNodeElem.ConnectionPoints.ContainsKey(OtherElement))
                            MM_Log.LogEvent(new MM_Event_ItemNotFound(FoundNodeElem, "ConnectionPoint=" + OtherElement.ToString(), FoundNodeElem.GetType().GetProperty("ConnectionPoints"), OtherElement));
                        else
                        {

                            List<MM_OneLine_PokePoint> NewPokes = new List<MM_OneLine_PokePoint>(FoundNodeElem.ConnectionPoints[OtherElement]);


                            //Determine the orientation of the pricing vector
                            double Dist, Angle, DeltaX, DeltaY;
                            bool WithinX, WithinY;
                            MM_OneLine_TuningParameter.ComputeDistanceAndAngle(NewPokes[NewPokes.Count - 1].Bounds, OtherElement.Bounds, out Dist, out Angle, out DeltaX, out DeltaY, out WithinX, out WithinY);
                            MM_OneLine_Element.enumOrientations Orientation;
                            if (Math.Abs(DeltaX) > Math.Abs(DeltaY))
                                Orientation = (DeltaX > 0 ? MM_OneLine_Element.enumOrientations.Right : MM_OneLine_Element.enumOrientations.Left);
                            else
                                Orientation = (DeltaY > 0 ? MM_OneLine_Element.enumOrientations.Up : MM_OneLine_Element.enumOrientations.Down);

                            Point TargetCenter = MM_OneLine_Element.CenterRect(OtherElement.Bounds);
                            Point LastPokeCenter = MM_OneLine_Element.CenterRect(NewPokes[NewPokes.Count - 1].Bounds);
                            Point OutCenter = new Point((TargetCenter.X + LastPokeCenter.X) / 2, (TargetCenter.Y + LastPokeCenter.Y) / 2);

                            MM_OneLine_PricingVector OutVector = new MM_OneLine_PricingVector(PV, FoundNodeElem, olView.DisplayParameters, Orientation);
                            Point CenterVector = MM_OneLine_Element.CenterRect(OutVector.DisplayRectangle);

                            OutVector.Location = new Point(OutCenter.X - CenterVector.X, OutCenter.Y - CenterVector.Y);
                            Vectors.Add(OutVector);
                            OutVector.Name = Elem.Name;
                            OutVector.Tag = Elem;

                            //Set up the descriptor                                                
                            MM_OneLine_Descriptor NewDesc = new MM_OneLine_Descriptor(OutVector, olView.DisplayParameters);
                            OutVector.Descriptor = NewDesc;
                            NewDesc.RecomputeSize();
                            Point CenterElem = MM_OneLine_Element.CenterRect(OutVector.Bounds);
                            if (Orientation == MM_OneLine_Element.enumOrientations.Unknown || Orientation == MM_OneLine_Element.enumOrientations.Vertical || Orientation == MM_OneLine_Element.enumOrientations.Up || Orientation == MM_OneLine_Element.enumOrientations.Down)
                                NewDesc.Location = new Point(OutVector.Right + 2, CenterElem.Y - MM_OneLine_Element.CenterRect(NewDesc.DisplayRectangle).Y);
                            else
                                NewDesc.Location = new Point(CenterElem.X - MM_OneLine_Element.CenterRect(NewDesc.DisplayRectangle).X, OutVector.Bottom + 2);


                            OutVector.ForeColor = NewDesc.ForeColor = Color.Gray;

                            NewPokes.Add(OutVector);
                            FoundNodeElem.ConnectionPoints[OtherElement] = NewPokes.ToArray();
                        }
                    }
                }
            olView.DisplayParameters.HandleAddedElements(Vectors.ToArray());


           


            return Vectors;
        }
        
        /// <summary>
        /// Run through the topology, to locate any errors
        /// </summary>
        /// <param name="olView">The one-line viewer</param>
        /// <param name="BaseElement">The CIM base element</param>
        /// <param name="Errors">The number of found errors</param>
        /// <param name="Warnings">The number of found warnings</param>
        public static string ValidateTopology(MM_OneLine_Viewer olView, CIM_Element BaseElement, out int Errors, out int Warnings)
        {
            List<String> ExcludeTypes = new List<string>(5);
            ExcludeTypes.Add("BusbarSection");
            ExcludeTypes.Add("Bay");
            ExcludeTypes.Add("ConnectivityNodeGroup");
            ExcludeTypes.Add("SynchrocheckRelay");
            ExcludeTypes.Add("FrequencyRelay");

            DateTime StartTime = DateTime.Now;
            Errors = 0;
            Warnings = 0;
            MM_Element BaseStation = MM_Database_CIM.LocateElementByTEID(Convert.ToUInt64(BaseElement.TEID), olView.AddedElements);
            StringBuilder sB = new StringBuilder();
            sB.AppendLine("Topology validation ran on " + DateTime.Now.ToString());
            sB.AppendLine("Base element: " + BaseElement.Type.Split(':')[1] + " " + BaseElement.Name);
            
            //First, go through an determine whether all elements are in place
            foreach (CIM_Element Elem in BaseElement.Elements)
                if (!String.IsNullOrEmpty(Elem.TEID))
            {
                MM_Element MMElem = MM_Database_CIM.LocateElement(Elem, olView.AddedElements);
                MM_OneLine_Element FoundElem = null;
                if (MMElem != null && MMElem.Name == "0004")
                    Console.WriteLine("hmm...");
                if (MMElem != null && !String.IsNullOrEmpty(Elem.TEID)  && !ExcludeTypes.Contains(Elem.Type.Split(':')[1]))
                    if (!olView.TransformerWindings.ContainsKey(MMElem) && !olView.DisplayElements.ContainsKey(MMElem) && !olView.DisplayNodes.ContainsKey(MMElem))                    
                        sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is in the CIM representation of the substation, but not on the one-line.", ++Errors, Elem.Type.Split(':')[1], Elem.Name));
                    else if (olView.DisplayElements.ContainsKey(MMElem) && (FoundElem = olView.DisplayElements[MMElem]).Descriptor == null)
                    {
                        
                        //if (FoundElem is MM_OneLine_Node && (FoundElem.BaseElement as MM_Node).BusbarSection != null)
                        //    sB.AppendLine(String.Format("WRN {0:000}: {1} {2} is in the one-line and is associated with busbar section {3}, but does not have a descriptor.", ++Warnings, Elem.Type.Split(':')[1], Elem.Name, (FoundElem.BaseElement as MM_Node).BusbarSection.Name));
                        //else
                            if (FoundElem is MM_OneLine_PricingVector || (FoundElem is MM_OneLine_PokePoint == false))
                            sB.AppendLine(String.Format("WRN {0:000}: {1} {2} is in the one-line, but does not have a descriptor.", ++Warnings, Elem.Type.Split(':')[1], Elem.Name));
                    }
                    else
                    {
                        if (olView.DisplayElements.ContainsKey(MMElem) && olView.DisplayElements[MMElem].Descriptor != null && !olView.Descriptors.ContainsKey(MMElem))
                        {
                            sB.AppendLine(String.Format("WRN {0:000} (auto-fixed): {1} {2} is in the one-line and has a descriptor, but it's not stored in the descriptor collection.", ++Warnings, Elem.Type.Split(':')[1], Elem.Name));
                            olView.Descriptors.Add(MMElem, olView.DisplayElements[MMElem].Descriptor);
                        }
                        if (olView.DisplayElements.ContainsKey(MMElem) && olView.DisplayElements[MMElem].SecondaryDescriptor != null && !olView.SecondaryDescriptors.ContainsKey(MMElem))
                        {
                            sB.AppendLine(String.Format("WRN {0:000} (auto-fixed): {1} {2} is in the one-line and has a secondary descriptor, but it's not stored in the secondary descriptor collection.", ++Warnings, Elem.Type.Split(':')[1], Elem.Name));
                            olView.SecondaryDescriptors.Add(MMElem, olView.DisplayElements[MMElem].SecondaryDescriptor);
                        }
                    }
            }

            //Check for elements in the one-line that aren't in CIM
            foreach (MM_Element Elem in olView.DisplayElements.Keys)
                if (MM_Database_CIM.LocateElementByTEID(Elem.TEID, olView.AddedElements) == null)
                    sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced in the one-line, but not in the CIM representation of the substation (by TEID)", ++Errors, Elem.ElemType.Name, Elem.Name));
                else if (Elem is MM_Line && Array.IndexOf((Elem as MM_Line).ConnectedStations, BaseStation) == -1)
                        sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced in the one-line, but it does not contain the base substation {3}.", ++Errors, Elem.ElemType.Name, Elem.Name, BaseStation.Name));
                else if (Elem is MM_Line == false && Elem.Substation != BaseStation)
                        sB.AppendLine(String.Format("WRN {0:000}: {1} {2} is referenced in the one-line, but is not of the same substation as the base element {3}.", ++Warnings, Elem.ElemType.Name, Elem.Name, BaseStation.Name));


            //Now, go through all nodes, and determine whether they are in place.

            foreach (CIM_Element Node in BaseElement.Nodes)
                if (!olView.DisplayNodes.ContainsKey(MM_Database_CIM.LocateElement(Node, olView.AddedElements)))
                    sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is in the CIM representation of the substation, but not on the one-line.", ++Errors, Node.Type.Split(':')[1], Node.Name));
                else
                {
                    MM_OneLine_Node MMNode = olView.DisplayNodes[MM_Database_CIM.LocateElement(Node, olView.AddedElements)];
                    String RN = Node["etx:ElectricalBus>etx:ResourceNode>name"];
                    
                    if (!string.Equals(MMNode.ResourceNode , RN, StringComparison.CurrentCultureIgnoreCase))
                        sB.AppendLine(String.Format("ERR {0:000}: The resource name for {1} {2} differs: MM={3}, CIM={4}", ++Errors, MMNode.BaseElement.ElemType.Name, MMNode.BaseElement.Name, MMNode.ResourceNode, RN));
                    if (!String.IsNullOrEmpty(RN))
                        if (MMNode is MM_OneLine_Node && (MMNode as MM_OneLine_Node).SecondaryDescriptor == null)
                        sB.AppendLine(String.Format("WRN {0:000}: {1} {2} contains resource node {3}, but no secondary descriptor is present.", ++Errors, MMNode.BaseElement.ElemType.Name, MMNode.BaseElement.Name, RN));
                    else if (MMNode is MM_OneLine_Node)
                    {
                        bool FoundRN = false;
                        foreach (MM_OneLine_PokePoint[] Pokes in MMNode.ConnectionPoints.Values)
                            foreach (MM_OneLine_PokePoint Poke in Pokes)
                                if (Poke is MM_OneLine_PricingVector == false && Poke.SecondaryDescriptor != null)
                                    FoundRN = true;
                        if (!FoundRN)
                            sB.AppendLine(String.Format("WRN {0:000}: {1} {2} contains resource node {3}, but no secondary descriptor is present within the poke points", ++Errors, MMNode.BaseElement.ElemType.Name, MMNode.BaseElement.Name, RN));
                    }                    


                    
                    
                        




                    //If we have a match, check the node elements against CIM
                    foreach (CIM_Element NodeElem in Node.Elements)
                    {
                        MM_Element MMNodeElem = MM_Database_CIM.LocateElement(NodeElem, olView.AddedElements);

                        //If we have a pricing vector, we have special logic to determine whether it is in place                        
                        if (MMNodeElem is MM_PricingVector)
                        {


                            MM_Element OtherElem = (MMNodeElem as MM_PricingVector).OtherElement;
                            MM_Element NodeElem2 = (MMNodeElem as MM_PricingVector).NodeElement;

                            if (OtherElem.ElemType.Name == "TransformerWinding" && olView.TransformerWindings.ContainsKey(OtherElem))
                                OtherElem = olView.TransformerWindings[OtherElem].BaseElement;
                            

                            if (NodeElem2 != MMNode.BaseElement)
                                sB.AppendLine(String.Format("ERR {0:000} : {1} {2} is a component of {3} {4}, but instead it is contained in {5} {6}.", ++Errors, MMNodeElem.ElemType.Name, MMNodeElem.Name,NodeElem2.ElemType.Name, NodeElem2.Name,MMNode.BaseElement.ElemType.Name, MMNode.BaseElement.Name));
                            else if (!olView.DisplayElements.ContainsKey(OtherElem))
                                sB.AppendLine(String.Format("ERR {0:000} : {1} {2} should be connected to {3} {4}, but it is not on the display.", ++Errors, MMNodeElem.ElemType.Name, MMNodeElem.Name, OtherElem.ElemType.Name, OtherElem.Name));
                            else if (!MMNode.ConnectionPoints.ContainsKey(olView.DisplayElements[OtherElem]))
                                sB.AppendLine(String.Format("ERR {0:000} : {1} {2} should be connected to {3} {4}, but it is not.", ++Errors, MMNodeElem.ElemType.Name, MMNodeElem.Name, OtherElem.ElemType.Name, OtherElem.Name));
                            else
                            {
                                int VectorLocation = -1;
                                for (int a = 0; a < MMNode.ConnectionPoints[olView.DisplayElements[OtherElem]].Length; a++)
                                    if (MMNode.ConnectionPoints[olView.DisplayElements[OtherElem]][a].BaseElement == MMNodeElem)
                                        VectorLocation = a;
                                
                                if (VectorLocation == -1)
                                    sB.AppendLine(String.Format("ERR {0:000} : {1} {2}'s vector is not found between {3} {4} and {5} {6}.", ++Errors, MMNodeElem.ElemType.Name, MMNodeElem.Name, NodeElem2.ElemType.Name, NodeElem2.Name, OtherElem.ElemType.Name, OtherElem.Name));
                                else
                                {
                                    MM_OneLine_PricingVector Vector = MMNode.ConnectionPoints[olView.DisplayElements[OtherElem]][VectorLocation] as MM_OneLine_PricingVector;
                                    if (Vector.IsPositive != Convert.ToBoolean(NodeElem["etx:PricingVector.positiveFlowIn"]))
                                        sB.AppendLine(String.Format("ERR {0:000} : {1} {2}'s vector has an opposite 'IsPositive' value from CIM.", ++Errors, MMNodeElem.ElemType.Name, MMNodeElem.Name));

                                    if (Vector.EPSMeter != MM_Database_CIM.LocateElement(NodeElem.Parse("etx:EPSMeter"), olView.AddedElements))
                                        sB.AppendLine(String.Format("ERR {0:000} : {1} {2} refers to EPS Meter {3}, not {4}.", ++Errors, MMNodeElem.ElemType.Name, MMNodeElem.Name, Vector.EPSMeter.Name, NodeElem.Parse("etx:EPSMeter").Name));

                                    if (Vector.RID.Equals(NodeElem.Parse("etx:EPSMeter")["etx:EPSMeter.RID"], StringComparison.CurrentCultureIgnoreCase) == false)
                                        sB.AppendLine(String.Format("ERR {0:000} : {1} {2} refers to EPS Meter RID {3}, not {4}.", ++Errors, MMNodeElem.ElemType.Name, MMNodeElem.Name, Vector.RID, MM_Repository.TitleCase(NodeElem.Parse("etx:EPSMeter")["etx:EPSMeter.RID"])));
                                }
                            }
                        }
                        else if (olView.DisplayElements.ContainsKey(MMNodeElem) && !MMNode.ConnectionPoints.ContainsKey(olView.DisplayElements[MMNodeElem]))
                            sB.AppendLine(String.Format("ERR {0:000} : {1} {2} references {3} {4}, but it is not referenced in the one-line.", ++Errors, MMNode.BaseElement.ElemType.Name, MMNode.BaseElement.Name, NodeElem.Type.Split(':')[1], NodeElem.Name));
                    }
                }

            foreach (MM_Element Elem in olView.DisplayNodes.Keys)
                if (MM_Database_CIM.LocateElementByTEID(Elem.TEID, olView.AddedElements) == null)
                    sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced in the one-line, but not in the CIM representation of the substation (by TEID)", ++Errors, Elem.ElemType.Name, Elem.Name));
                else if (MM_Database_CIM.LocateElementByTEID(Elem.TEID, olView.AddedElements).Substation != BaseStation)
                    sB.AppendLine(String.Format("WRN {0:000}: {1} {2} is referenced in the one-line, but is not of the same substation as the base element {3}.", ++Warnings, Elem.ElemType.Name, Elem.Name, BaseStation.Name));


            //Check our nodes to make sure we have no pricing vectors
            foreach (MM_OneLine_Node Node in olView.DisplayNodes.Values)
            {
                foreach (MM_OneLine_Element Elem in Node.ConnectionPoints.Keys)
                    if (Elem.BaseElement.ElemType.Name == "PricingVector")
                        sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is referenced as a target of a node, while it should instead be on a trajectory to another element.", ++Errors, Elem.BaseElement.ElemType.Name, Elem.Name));                
                    
            }



            //Make sure all elements are appropriately referenced
            foreach (MM_OneLine_Element Elem in olView.PanelControls)
                if (Elem is MM_OneLine_SecondaryDescriptor)
                {
                    MM_OneLine_SecondaryDescriptor SElem = Elem as MM_OneLine_SecondaryDescriptor;
                    if (SElem.ParentElement == null)
                        sB.AppendLine(String.Format("ERR {0:000}: An unlinked secondary descriptor has been located.", ++Errors));
                    else
                    {
                        if (!olView.SecondaryDescriptors.ContainsKey(SElem.ParentElement.BaseElement))
                            sB.AppendLine(String.Format("ERR {0:000}: Secondary Descriptor for {1} {2} ({3}) is not stored in the secondary descriptors collection.", ++Errors, SElem.ParentElement.BaseElement.ElemType.Name, SElem.ParentElement.BaseElement.Name, SElem.ImageText));
                        else if (olView.SecondaryDescriptors[SElem.ParentElement.BaseElement] != SElem)
                            sB.AppendLine(String.Format("ERR {0:000}: Secondary Descriptor for {1} {2} ({3}) references a different secondary descriptor than that connected to the element.", ++Errors, SElem.ParentElement.BaseElement.ElemType.Name, SElem.ParentElement.BaseElement.Name, SElem.ImageText));
                        if (SElem.ParentElement.SecondaryDescriptor != SElem)
                            sB.AppendLine(String.Format("ERR {0:000}: Secondary Descriptor for {1} {2}'s parent references a different secondary descriptor ({3}).", ++Errors, SElem.ParentElement.BaseElement.ElemType.Name, SElem.ParentElement.BaseElement.Name, SElem.ImageText));
                    }
                }
                else if (Elem is MM_OneLine_Descriptor)
                {
                    MM_OneLine_Descriptor DElem = Elem as MM_OneLine_Descriptor;
                    if (DElem.ParentElement == null)
                        sB.AppendLine(String.Format("ERR {0:000}: An unlinked descriptor has been located.", ++Errors));
                    else
                    {
                        if (!olView.Descriptors.ContainsKey(DElem.ParentElement.BaseElement))
                            sB.AppendLine(String.Format("ERR {0:000}: Descriptor for {1} {2} ({3}) is not stored in the descriptors collection.", ++Errors, DElem.ParentElement.BaseElement.ElemType.Name, DElem.ParentElement.BaseElement.Name, DElem.ImageText));
                        else if (olView.Descriptors[DElem.ParentElement.BaseElement] != DElem)
                            sB.AppendLine(String.Format("ERR {0:000}: Descriptor for {1} {2} ({3}) references a different descriptor than that connected to the element.", ++Errors, DElem.ParentElement.BaseElement.ElemType.Name, DElem.ParentElement.BaseElement.Name, DElem.ImageText));
                        if (DElem.ParentElement.Descriptor != DElem)
                            sB.AppendLine(String.Format("ERR {0:000}: Descriptor for {1} {2}'s parent references a different descriptor. ({3})", ++Errors, DElem.ParentElement.BaseElement.ElemType.Name, DElem.ParentElement.BaseElement.Name, DElem.ImageText));
                    }
                }
                else if (Elem is MM_OneLine_Node)
                {
                    if (!olView.DisplayElements.ContainsKey(Elem.BaseElement))
                        sB.AppendLine(String.Format("ERR {0:000}: Node {1} {2} is not found in the collection of elements.", ++Errors, Elem.BaseElement.ElemType.Name, Elem.BaseElement.Name));
                    if (!olView.DisplayNodes.ContainsKey(Elem.BaseElement))
                        sB.AppendLine(String.Format("ERR {0:000}: Node {1} {2} is not found in the collection of nodes.", ++Errors, Elem.BaseElement.ElemType.Name, Elem.BaseElement.Name));
                }
                else if (Elem is MM_OneLine_PricingVector)
                    if (!olView.DisplayElements.ContainsKey(Elem.BaseElement))
                        sB.AppendLine(String.Format("ERR {0:000}: {1} {2} is not found in the collection of elements.", ++Errors, Elem.BaseElement.ElemType.Name, Elem.BaseElement.Name));

            sB.AppendLine();
            sB.AppendLine(String.Format("Topology validation completed in {0}, with {1:#,##0} errors and {2:#,##0} warnings", DateTime.Now - StartTime, Errors, Warnings));
            return sB.ToString();
        }

    }
}